aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLode Vandevenne <lvandeve@users.noreply.github.com>2019-01-18 18:30:51 +0100
committerGitHub <noreply@github.com>2019-01-18 18:30:51 +0100
commitef109ddf164911cf1e5612e90b4a619839a1e3ca (patch)
treee5fbd911fcbd388a71b282a41115bc72f5ffe094
parent1ca477efaa485aac848ab6a3de801f01b255f179 (diff)
parente56f4dc8f7f7dfb47f06819749dc75f91c4bfdce (diff)
downloadzopfli-ef109ddf164911cf1e5612e90b4a619839a1e3ca.tar.gz
Merge pull request #158 from davidsansome/cgo
Add a CGO wrapper
-rw-r--r--go/zopfli/zopfli.go58
-rw-r--r--go/zopfli/zopfli_test.go69
-rw-r--r--go/zopflipng/testdata/zoidberg.pngbin0 -> 25709 bytes
-rw-r--r--go/zopflipng/zopflipng.go86
-rw-r--r--go/zopflipng/zopflipng_test.go35
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
new file mode 100644
index 0000000..434d918
--- /dev/null
+++ b/go/zopflipng/testdata/zoidberg.png
Binary files differ
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")
+ }
+}