diff options
author | Lode Vandevenne <lvandeve@users.noreply.github.com> | 2019-01-18 18:30:51 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2019-01-18 18:30:51 +0100 |
commit | ef109ddf164911cf1e5612e90b4a619839a1e3ca (patch) | |
tree | e5fbd911fcbd388a71b282a41115bc72f5ffe094 | |
parent | 1ca477efaa485aac848ab6a3de801f01b255f179 (diff) | |
parent | e56f4dc8f7f7dfb47f06819749dc75f91c4bfdce (diff) | |
download | zopfli-ef109ddf164911cf1e5612e90b4a619839a1e3ca.tar.gz |
Merge pull request #158 from davidsansome/cgo
Add a CGO wrapper
-rw-r--r-- | go/zopfli/zopfli.go | 58 | ||||
-rw-r--r-- | go/zopfli/zopfli_test.go | 69 | ||||
-rw-r--r-- | go/zopflipng/testdata/zoidberg.png | bin | 0 -> 25709 bytes | |||
-rw-r--r-- | go/zopflipng/zopflipng.go | 86 | ||||
-rw-r--r-- | go/zopflipng/zopflipng_test.go | 35 |
5 files changed, 248 insertions, 0 deletions
diff --git a/go/zopfli/zopfli.go b/go/zopfli/zopfli.go new file mode 100644 index 0000000..0cb78aa --- /dev/null +++ b/go/zopfli/zopfli.go @@ -0,0 +1,58 @@ +// Copyright 2019 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Package zopfli provides a simple Go interface for Zopfli compression. +package zopfli + +/* +#cgo LDFLAGS: -lzopfli -lm +#include <limits.h> // for INT_MAX +#include <stdlib.h> // for free() +#include <string.h> // for memmove() +#include "zopfli.h" +*/ +import "C" +import "unsafe" + +// Zopfli can't handle empty input, so we use a static result. +const emptyGzip = "\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x03\x00\x00\x00\x00\x00\x00\x00\x00\x00" + +// Gzip compresses data with Zopfli using default settings and gzip format. +// The Zopfli library does not return errors, and there are no (detectable) +// failure cases, hence no error return. +func Gzip(inputSlice []byte) []byte { + var options C.struct_ZopfliOptions + C.ZopfliInitOptions(&options) + + inputSize := (C.size_t)(len(inputSlice)) + if inputSize == 0 { + return []byte(emptyGzip) + } + input := (*C.uchar)(unsafe.Pointer(&inputSlice[0])) + var compressed *C.uchar + var compressedLength C.size_t + + C.ZopfliCompress(&options, C.ZOPFLI_FORMAT_GZIP, + input, inputSize, + &compressed, &compressedLength) + defer C.free(unsafe.Pointer(compressed)) + + // GoBytes only accepts int, not C.size_t. The code below does the same minus + // protection against zero-length values, but compressedLength is never 0 due + // to headers. + result := make([]byte, compressedLength) + C.memmove(unsafe.Pointer(&result[0]), unsafe.Pointer(compressed), + compressedLength) + return result +} diff --git a/go/zopfli/zopfli_test.go b/go/zopfli/zopfli_test.go new file mode 100644 index 0000000..4d61703 --- /dev/null +++ b/go/zopfli/zopfli_test.go @@ -0,0 +1,69 @@ +// Copyright 2019 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +package zopfli + +import ( + "bytes" + "compress/gzip" + "io/ioutil" + "math/rand" + "strings" + "testing" +) + +func getRandomBytes(length uint64) []byte { + rng := rand.New(rand.NewSource(1)) // Make test repeatable. + data := make([]byte, length) + for i := uint64(0); i < length; i++ { + data[i] = (byte)(rng.Int()) + } + return data +} + +// TestGzip verifies that Gzip compresses data correctly. +func TestGzip(t *testing.T) { + compressibleString := "compressthis" + strings.Repeat("_foobar", 1000) + "$" + + for _, test := range []struct { + name string + data []byte + maxSize int + }{ + {"compressible string", []byte(compressibleString), 500}, + {"random binary data", getRandomBytes(3000), 3100}, + {"empty string", []byte(""), 20}, + } { + compressed := Gzip(test.data) + gzipReader, err := gzip.NewReader(bytes.NewReader(compressed)) + if err != nil { + t.Errorf("%s: gzip.NewReader: got error %v, expected no error", + test.name, err) + continue + } + decompressed, err := ioutil.ReadAll(gzipReader) + if err != nil { + t.Errorf("%s: reading gzip stream: got error %v, expected no error", + test.name, err) + continue + } + if bytes.Compare(test.data, decompressed) != 0 { + t.Errorf("%s: mismatch between input and decompressed data", test.name) + continue + } + if test.maxSize > 0 && len(compressed) > test.maxSize { + t.Errorf("%s: compressed data is %d bytes, expected %d or less", + test.name, len(compressed), test.maxSize) + } + } +} diff --git a/go/zopflipng/testdata/zoidberg.png b/go/zopflipng/testdata/zoidberg.png Binary files differnew file mode 100644 index 0000000..434d918 --- /dev/null +++ b/go/zopflipng/testdata/zoidberg.png diff --git a/go/zopflipng/zopflipng.go b/go/zopflipng/zopflipng.go new file mode 100644 index 0000000..f532321 --- /dev/null +++ b/go/zopflipng/zopflipng.go @@ -0,0 +1,86 @@ +// Copyright 2019 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +package zopflipng + +import ( + "fmt" +) + +/* +#cgo LDFLAGS: -lzopflipng -lzopfli -lstdc++ -lm +#include <stdlib.h> +#include <string.h> +#include "zopflipng_lib.h" +*/ +import "C" +import "unsafe" + +// Options allows overriding of some internal parameters. +type Options struct { + LossyTransparent bool + Lossy8bit bool + NumIterations int + NumIterationsLarge int +} + +// NewOptions creates an options struct with the default parameters. +func NewOptions() *Options { + ret := &Options{ + LossyTransparent: false, + Lossy8bit: false, + NumIterations: 15, + NumIterationsLarge: 5, + } + return ret +} + +// Compress recompresses a PNG using Zopfli. +func Compress(inputSlice []byte) ([]byte, error) { + return CompressWithOptions(inputSlice, NewOptions()) +} + +// CompressWithOptions allows overriding some internal parameters. +func CompressWithOptions(inputSlice []byte, options *Options) ([]byte, error) { + cOptions := createCOptions(options) + input := (*C.uchar)(unsafe.Pointer(&inputSlice[0])) + inputSize := (C.size_t)(len(inputSlice)) + var compressed *C.uchar + var compressedLength C.size_t + errCode := int(C.CZopfliPNGOptimize(input, inputSize, &cOptions, 0, &compressed, &compressedLength)) + defer C.free(unsafe.Pointer(compressed)) + if errCode != 0 { + return nil, fmt.Errorf("ZopfliPng failed with code: %d", errCode) + } + + result := make([]byte, compressedLength) + C.memmove(unsafe.Pointer(&result[0]), unsafe.Pointer(compressed), compressedLength) + return result, nil +} + +func createCOptions(options *Options) C.struct_CZopfliPNGOptions { + var cOptions C.struct_CZopfliPNGOptions + C.CZopfliPNGSetDefaults(&cOptions) + cOptions.lossy_transparent = boolToInt(options.LossyTransparent) + cOptions.lossy_8bit = boolToInt(options.Lossy8bit) + cOptions.num_iterations = C.int(options.NumIterations) + cOptions.num_iterations_large = C.int(options.NumIterationsLarge) + return cOptions +} + +func boolToInt(b bool) C.int { + if b { + return C.int(1) + } + return C.int(0) +} diff --git a/go/zopflipng/zopflipng_test.go b/go/zopflipng/zopflipng_test.go new file mode 100644 index 0000000..8f3d423 --- /dev/null +++ b/go/zopflipng/zopflipng_test.go @@ -0,0 +1,35 @@ +// Copyright 2019 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +package zopflipng + +import ( + "io/ioutil" + "testing" +) + +// TestCompress verifies that ZopfliPng compresses PNGs correctly. +func TestCompress(t *testing.T) { + path := "testdata/zoidberg.png" + contents, err := ioutil.ReadFile(path) + if err != nil { + t.Errorf("Failed to load testdata: %s", path) + } + compressed, err := Compress(contents) + if err != nil { + t.Error("ZopfliPNG failed: ", err) + } + if len(compressed) >= len(contents) { + t.Error("ZopfliPNG did not compress png") + } +} |