pax_global_header00006660000000000000000000000064145457015030014516gustar00rootroot0000000000000052 comment=715f0ef63a381b7ad872e5a1f56224889ef47406 golang-github-antonini-golibjpegturbo-0.0~git20141208.c03a2fa/000077500000000000000000000000001454570150300235775ustar00rootroot00000000000000golang-github-antonini-golibjpegturbo-0.0~git20141208.c03a2fa/.gitignore000066400000000000000000000004121454570150300255640ustar00rootroot00000000000000# Compiled Object files, Static and Dynamic libs (Shared Objects) *.o *.a *.so # Folders _obj _test # Architecture specific extensions/prefixes *.[568vq] [568vq].out *.cgo1.go *.cgo2.c _cgo_defun.c _cgo_gotypes.go _cgo_export.* _testmain.go *.exe *.test *.prof golang-github-antonini-golibjpegturbo-0.0~git20141208.c03a2fa/LICENSE000066400000000000000000000020771454570150300246120ustar00rootroot00000000000000The MIT License (MIT) Copyright (c) 2014 Krzysztof Kowalczyk Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. golang-github-antonini-golibjpegturbo-0.0~git20141208.c03a2fa/README.md000066400000000000000000000033421454570150300250600ustar00rootroot00000000000000## Read JPEG images in Go, quickly This library is the fastest possible way to decode and encode JPEG images in Go. We achieve this via cgo bindings to [libjpeg-turbo](http://libjpeg-turbo.virtualgl.org) library. The exact speed depends on the image and CPU. On Mac Book Pro, compared to `image/jpeg` standard library, golibjpegturbo is: * 6x faster when decoding * 1.7x faster when encoding at quality level of 90% You can rerun the benchmark on your machine with `go test -bench=.` Additionally, unlike `image/jpeg`, this library can read JPEG images in CMYK format. ## Setup Before you import library, you need to install libjpeg-turbo. On Ubuntu: `sudo apt-get install libjpeg-turbo8-dev`. On Mac OS X: `brew install libjpeg-turbo` ## Usage `go get github.com/kjk/golibjpegturbo` The API is the same as `image/libjpeg`: ```go import "golibjpegturbo" func decode(r io.Reader) (image.Image, error) { return golibjpegturbo.Decode(r) } func encode(img image.Image) ([]byte, error) { options := &golibjpegturbo.Options{Quality: 90} var buf bytes.Buffer err = golibjpegturbo.Encode(&buf, decodedImg, options) if err != nil { return nil, err } return buf.Bytes(), nil } ``` More docs: http://godoc.org/github.com/kjk/golibjpegturbo ## Running a stress test There's a stress test. If you have a directory with images, you can do `go run stress_test/main.go -dir=$directory`. The test loops infinitely and decodes/encodes the images to verify there are no problems caused by incorrect `cgo` usage. ## License MIT. Written by [Krzysztof Kowalczyk](http://blog.kowalczyk.info/). Inspired by [lye/libjpeg](https://github.com/lye/libjpeg) and [go-thumber](https://github.com/pixiv/go-thumber/tree/master/jpeg). golang-github-antonini-golibjpegturbo-0.0~git20141208.c03a2fa/decode.go000066400000000000000000000216651454570150300253630ustar00rootroot00000000000000/* Package golibjpegturbo is the fastest way to decode and encode JPEG images in Go. We achieve this via cgo bindings to http://libjpeg-turbo.virtualgl.org library. The exact speed depends on the image and CPU. On Mac Book Pro, compared to image/jpeg standard library, golibjpegturbo is: * 6x faster when decoding * 1.7x faster when encoding at quality level of 90% Before you import library, you need to install libjpeg-turbo. On Ubuntu: sudo apt-get install libjpeg-turbo8-dev. On Mac OS X: brew install libjpeg-turbo */ package golibjpegturbo // Note: on mac (darwin) /usr/local/opt symlinks to the latest installed version // e.g. /usr/local/Cellar/jpeg-turbo/1.3.1 /* #cgo linux LDFLAGS: -ljpeg #cgo darwin LDFLAGS: -L/usr/local/opt/jpeg-turbo/lib -ljpeg #cgo darwin CFLAGS: -I/usr/local/opt/jpeg-turbo/include #include #include #include #include void error_panic(j_common_ptr cinfo); */ import "C" import ( "fmt" "image" "io" "io/ioutil" "reflect" "unsafe" ) // JpegInfo contains information about JPEG image. type JpegInfo struct { Components int ColorSpace int Width int Height int ColorSpaceString string } /* valid combinations of number of components vs. color space: JCS_GRAYSCALE => 1 JCS_RGB, case JCS_YCbCr => 3 JCS_CMYK, JCS_YCCK => 4 */ func colorSpaceToString(cs int) string { if cs == int(C.JCS_GRAYSCALE) { return "grayscale" } if cs == int(C.JCS_YCbCr) { return "ycbcr" } if cs == int(C.JCS_RGB) { return "rgb" } if cs == int(C.JCS_CMYK) { return "cmyk" } if cs == int(C.JCS_YCCK) { return "ycck" } // those seem only to be available for out_color_space i.e. decoded space if cs == int(C.JCS_EXT_RGB) { return "extrgb" } if cs == int(C.JCS_EXT_RGBX) { return "extrgbx" } if cs == int(C.JCS_EXT_BGR) { return "extbgr" } if cs == int(C.JCS_EXT_BGRX) { return "extbgrx" } if cs == int(C.JCS_EXT_XBGR) { return "extxbgr" } if cs == int(C.JCS_EXT_XRGB) { return "extxrgb" } if cs == int(C.JCS_EXT_RGBA) { return "extrgba" } if cs == int(C.JCS_EXT_BGRA) { return "extbgra" } if cs == int(C.JCS_EXT_ABGR) { return "extabgr" } if cs == int(C.JCS_EXT_ARGB) { return "extargb" } return "unknown" } // GetJpegInfo returns information about a JPEG image. func GetJpegInfo(d []byte) (info *JpegInfo, err error) { defer func() { if r := recover(); r != nil { info = nil var ok bool err, ok = r.(error) if !ok { err = fmt.Errorf("JPEG error: %v", r) } } }() // those are allocated, not on stack because of // https://groups.google.com/forum/#!topic/golang-nuts/g4yBziN-MZQ cinfo := (*C.struct_jpeg_decompress_struct)(C.malloc(C.size_t(unsafe.Sizeof(C.struct_jpeg_decompress_struct{})))) defer C.free(unsafe.Pointer(cinfo)) cinfo.err = (*C.struct_jpeg_error_mgr)(C.malloc(C.size_t(unsafe.Sizeof(C.struct_jpeg_error_mgr{})))) defer C.free(unsafe.Pointer(cinfo.err)) C.jpeg_std_error(cinfo.err) cinfo.err.error_exit = (*[0]byte)(C.error_panic) C.jpeg_CreateDecompress(cinfo, C.JPEG_LIB_VERSION, C.size_t(unsafe.Sizeof(C.struct_jpeg_decompress_struct{}))) defer C.jpeg_destroy_decompress(cinfo) // TODO: should make a copy in C memory for GC safety? C.jpeg_mem_src(cinfo, (*C.uchar)(unsafe.Pointer(&d[0])), C.ulong(len(d))) res := C.jpeg_read_header(cinfo, C.TRUE) if res != C.JPEG_HEADER_OK { err = fmt.Errorf("C.jpeg_reader_header() failed with %d", int(res)) return } info = &JpegInfo{} info.Components = int(cinfo.num_components) info.ColorSpace = int(cinfo.jpeg_color_space) info.Width = int(cinfo.output_width) info.Height = int(cinfo.output_height) info.ColorSpaceString = colorSpaceToString(info.ColorSpace) return } func decodeToGray(cinfo *C.struct_jpeg_decompress_struct) image.Image { dx := int(cinfo.output_width) nBytes := dx // per one line, 1 byte per pixel dy := int(cinfo.output_height) img := image.NewGray(image.Rect(0, 0, dx, dy)) // Note: for even greater speed we could decode directly into img.Pix // but that might stop working when moving GC happens bufBytes := C.malloc(C.size_t(nBytes)) scanlines := C.JSAMPARRAY(unsafe.Pointer(&bufBytes)) buf := sliceFromCBytes(bufBytes, nBytes) for y := 0; y < dy; y++ { C.jpeg_read_scanlines(cinfo, scanlines, 1) off := y * img.Stride copy(img.Pix[off:off+nBytes], buf) } C.free(bufBytes) return img } // based on https://code.google.com/p/go-wiki/wiki/cgo // The important thing is that it creates []byte slice without copying // memory, for speed func sliceFromCBytes(p unsafe.Pointer, size int) []byte { hdr := reflect.SliceHeader{ Data: uintptr(p), Len: size, Cap: size, } slice := *(*[]byte)(unsafe.Pointer(&hdr)) return slice } // Note: for YCbCr could try to stay within YCbCr space, which might make the // decode->resize->encode loop faster if we avoid conversion to RGBA at any point // However, decoding to YCbCr is more complicated, because it has multiple // variants (4:2:2 etc.) func decodeToRgba(cinfo *C.struct_jpeg_decompress_struct) image.Image { dx := int(cinfo.output_width) nBytes := dx * 4 // per one line, 4 bytes of destination rgba per pixel dy := int(cinfo.output_height) img := image.NewRGBA(image.Rect(0, 0, dx, dy)) // Note: for even greater speed we could decode directly into img.Pix // but that might stop working when moving GC happens bufBytes := C.malloc(C.size_t(nBytes)) scanlines := C.JSAMPARRAY(unsafe.Pointer(&bufBytes)) buf := sliceFromCBytes(bufBytes, nBytes) for y := 0; y < dy; y++ { C.jpeg_read_scanlines(cinfo, scanlines, 1) off := y * img.Stride copy(img.Pix[off:off+nBytes], buf) } C.free(bufBytes) return img } // Source is 'Inverted CMYK' // See https://github.com/google/skia/blob/master/src/images/SkImageDecoder_libjpeg.cpp#L340 // for explanation func decodeCmykToRgba(cinfo *C.struct_jpeg_decompress_struct) image.Image { dx := int(cinfo.output_width) nBytes := dx * 4 // per one line, 4 bytes of destination rgba per pixel dy := int(cinfo.output_height) img := image.NewRGBA(image.Rect(0, 0, dx, dy)) bufBytes := C.malloc(C.size_t(nBytes)) scanlines := C.JSAMPARRAY(unsafe.Pointer(&bufBytes)) buf := sliceFromCBytes(bufBytes, nBytes) for y := 0; y < dy; y++ { C.jpeg_read_scanlines(cinfo, scanlines, 1) off := y * img.Stride srcOff := 0 for x := 0; x < dx; x++ { c := uint32(buf[srcOff]) srcOff++ m := uint32(buf[srcOff]) srcOff++ y := uint32(buf[srcOff]) srcOff++ k := uint32(buf[srcOff]) srcOff++ r := uint8(c * k / 255) g := uint8(m * k / 255) b := uint8(y * k / 255) img.Pix[off] = r off++ img.Pix[off] = g off++ img.Pix[off] = b off++ img.Pix[off] = 255 off++ } } C.free(bufBytes) return img } // DecodeData reads JPEG image from d and returns it as an image.Image. func DecodeData(d []byte) (img image.Image, err error) { defer func() { if r := recover(); r != nil { img = nil var ok bool err, ok = r.(error) if !ok { err = fmt.Errorf("JPEG error: %v", r) } } }() // those are allocated from heap, not on stack because of // https://groups.google.com/forum/#!topic/golang-nuts/g4yBziN-MZQ cinfo := (*C.struct_jpeg_decompress_struct)(C.malloc(C.size_t(unsafe.Sizeof(C.struct_jpeg_decompress_struct{})))) defer C.free(unsafe.Pointer(cinfo)) cinfo.err = (*C.struct_jpeg_error_mgr)(C.malloc(C.size_t(unsafe.Sizeof(C.struct_jpeg_error_mgr{})))) defer C.free(unsafe.Pointer(cinfo.err)) C.jpeg_std_error(cinfo.err) cinfo.err.error_exit = (*[0]byte)(C.error_panic) C.jpeg_CreateDecompress(cinfo, C.JPEG_LIB_VERSION, C.size_t(unsafe.Sizeof(C.struct_jpeg_decompress_struct{}))) defer C.jpeg_destroy_decompress(cinfo) // TODO: should make a copy in C memory for GC safety? C.jpeg_mem_src(cinfo, (*C.uchar)(unsafe.Pointer(&d[0])), C.ulong(len(d))) res := C.jpeg_read_header(cinfo, C.TRUE) if res != C.JPEG_HEADER_OK { img = nil err = fmt.Errorf("C.jpeg_reader_header() failed with %d", int(res)) return } nComp := int(cinfo.num_components) // if we're decoding YCbCr image, ask libjpeg to decode directly to RGBA // for speed (as opposed to converting to RGB and doing RGB -> RGBA in Go) // Note: I don't know if JCS_EXT_RGBA is pre-multiplied alpha (like Go) // but it shouldn't matter for decoding (jpeg doesn't have alpha // information, so alpha component should always end up 0xff) if nComp == 3 { cinfo.out_color_space = C.JCS_EXT_RGBA } C.jpeg_start_decompress(cinfo) defer C.jpeg_finish_decompress(cinfo) if nComp == 1 { img = decodeToGray(cinfo) } else if nComp == 3 { img = decodeToRgba(cinfo) } else if nComp == 4 { img = decodeCmykToRgba(cinfo) } else { err = fmt.Errorf("Invalid number of components (%d)", cinfo.num_components) } return } // Decode reads a JPEG image from r and returns it as an image.Image. func Decode(r io.Reader) (image.Image, error) { // loading the whole image is not ideal but doing callbacks is more complicated // so for now do the simple thing d, err := ioutil.ReadAll(r) if err != nil { return nil, err } return DecodeData(d) } golang-github-antonini-golibjpegturbo-0.0~git20141208.c03a2fa/decode_encode_test.go000066400000000000000000000131501454570150300277250ustar00rootroot00000000000000package golibjpegturbo import ( "bytes" "fmt" "image" "image/draw" "image/jpeg" "io/ioutil" "log" "net/http" "os" "os/exec" "testing" ) const ( // a random 683x1024 441kb image imageUrl = "https://farm1.staticflickr.com/47/139138903_3d9600174d_b_d.jpg" localFileName = "test.jpg" saveResized = false ) var ( imgData []byte imgGray []byte imgCmyk []byte decodedImg image.Image encodedImg []byte ) func init() { // download test file if doesn't exist if !PathExists(localFileName) { d := httpDl(imageUrl) err := ioutil.WriteFile(localFileName, d, 0644) panicIfErr(err) } d, err := ioutil.ReadFile(localFileName) if err != nil { log.Fatalf("ReadFile() failed with %q\n", err) } imgData = d r := bytes.NewReader(d) decodedImg, err = Decode(r) if err != nil { log.Fatalf("Decode() failed with %q\n", err) } if !convertExists() { return } imgGray = convertAndLoad("gray") imgCmyk = convertAndLoad("cmyk") } func httpDl(uri string) []byte { res, err := http.Get(uri) panicIfErr(err) d, err := ioutil.ReadAll(res.Body) res.Body.Close() panicIfErr(err) return d } // treats any error (e.g. lack of access due to permissions) as non-existence func PathExists(path string) bool { _, err := os.Stat(path) return err == nil } func panicIfErr(err error) { if err != nil { panic(err.Error()) } } // see if ImageMagick's convert utility exists func convertExists() bool { cmd := exec.Command("convert", "-version") if err := cmd.Run(); err != nil { return false } return true } // convert test.jpeg to a given colorspace and load the data using ImageMagick's // convert utility func convertAndLoad(colorSpace string) []byte { tmpPath := "tmp-" + colorSpace + ".jpeg" cmd := exec.Command("convert", "test.jpg", "-colorspace", colorSpace, tmpPath) err := cmd.Run() panicIfErr(err) d, err := ioutil.ReadFile(tmpPath) panicIfErr(err) err = os.Remove(tmpPath) panicIfErr(err) return d } func ImageToRgba(img image.Image) *image.RGBA { switch v := img.(type) { case *image.RGBA: return v } // all other images we convert to RGBA because rez only supports YCbCr and RGBA // and there are weird restrictions on YCbCr width/height that are not met by // many YCbCr images r := img.Bounds() r = image.Rect(0, 0, r.Dx(), r.Dy()) newImg := image.NewRGBA(r) draw.Draw(newImg, r, img, image.Point{}, draw.Src) return newImg } // not every image type has SubImage func SubImage(img image.Image, r image.Rectangle) image.Image { // fast path for types we expect to encounter in real life switch v := img.(type) { case *image.RGBA: return v.SubImage(r) case *image.Gray: return v.SubImage(r) case *image.Paletted: return v.SubImage(r) case *image.NRGBA: return v.SubImage(r) case *image.Gray16: return v.SubImage(r) case *image.YCbCr: return v.SubImage(r) } // slow path for everything else img2 := ImageToRgba(img) return img2.SubImage(r) } func saveEncoded(t *testing.T, img image.Image, path string) { if !saveResized { return } fout, err := os.Create(path) if err != nil { t.Fatal(err) } defer fout.Close() if err := Encode(fout, img, nil); err != nil { t.Fatal(err) } } func reencodeData(t *testing.T, imgData []byte, cs string) { var r image.Rectangle var img2 image.Image fin := bytes.NewBuffer(imgData) img, err := Decode(fin) if err != nil { t.Fatal(err) } saveEncoded(t, img, fmt.Sprintf("test_reencoded%s.jpg", cs)) // test bounds (0, 0, dx/2, dy/2) r = img.Bounds() r.Max.X = r.Max.X / 2 r.Max.Y = r.Max.Y / 2 img2 = SubImage(img, r) saveEncoded(t, img2, fmt.Sprintf("test_reencoded%s_0.jpg", cs)) // test bounds (dx/2, 0, dx, dy/2) r = img.Bounds() r.Min.X = r.Max.X / 2 r.Max.Y = r.Max.Y / 2 img2 = SubImage(img, r) saveEncoded(t, img2, fmt.Sprintf("test_reencoded%s_1.jpg", cs)) // test bounds (0, dy/2, dx/2, dy) r = img.Bounds() r.Max.X = r.Max.X / 2 r.Min.Y = r.Max.Y / 2 img2 = SubImage(img, r) saveEncoded(t, img2, fmt.Sprintf("test_reencoded%s_2.jpg", cs)) // test bounds (dx/2, dy/2, dx, dy) r = img.Bounds() r.Min.X = r.Max.X / 2 r.Min.Y = r.Max.Y / 2 img2 = SubImage(img, r) saveEncoded(t, img2, fmt.Sprintf("test_reencoded%s_3.jpg", cs)) // test taking half from center r = img.Bounds() dx := r.Dx() dy := r.Dy() r.Min.X = dx / 4 r.Max.X = r.Min.X + dx/2 r.Min.Y = dy / 4 r.Max.Y = r.Min.Y + dy/2 img2 = SubImage(img, r) saveEncoded(t, img2, fmt.Sprintf("test_reencoded%s_4.jpg", cs)) // test bounds taking 1 px from each side r = img.Bounds() r.Min.X = 1 r.Min.Y = 1 r.Max.X-- r.Max.Y-- img2 = SubImage(img, r) saveEncoded(t, img2, fmt.Sprintf("test_reencoded%s_5.jpg", cs)) } func TestReencode(t *testing.T) { reencodeData(t, imgData, "") if len(imgGray) > 0 { reencodeData(t, imgGray, "_gray") } if len(imgCmyk) > 0 { reencodeData(t, imgGray, "_cmyk") } } func BenchmarkDecode(b *testing.B) { var err error for n := 0; n < b.N; n++ { r := bytes.NewBuffer(imgData) decodedImg, err = Decode(r) if err != nil { b.Fatal(err) } } } func BenchmarkDecodeGo(b *testing.B) { var err error for n := 0; n < b.N; n++ { r := bytes.NewBuffer(imgData) decodedImg, err = jpeg.Decode(r) if err != nil { b.Fatal(err) } } } func BenchmarkEncode(b *testing.B) { var err error options := &Options{Quality: 90} var buf bytes.Buffer for n := 0; n < b.N; n++ { buf.Reset() err = Encode(&buf, decodedImg, options) if err != nil { b.Fatal(err) } } } func BenchmarkEncodeGo(b *testing.B) { var err error options := &jpeg.Options{Quality: 90} var buf bytes.Buffer for n := 0; n < b.N; n++ { buf.Reset() err = jpeg.Encode(&buf, decodedImg, options) if err != nil { b.Fatal(err) } } } golang-github-antonini-golibjpegturbo-0.0~git20141208.c03a2fa/encode.go000066400000000000000000000071331454570150300253670ustar00rootroot00000000000000package golibjpegturbo /* #include #include #include #include typedef unsigned char *PUCHAR; void error_panic(j_common_ptr cinfo); typedef struct { unsigned char *buf; unsigned long buf_size; } mem_helper; mem_helper *alloc_mem_helper(); */ import "C" import ( "fmt" "image" "io" "unsafe" ) // DefaultQuality is the default quality encoding parameter. const DefaultQuality = 75 // Options are the encoding parameters. // Quality ranges from 1 to 100 inclusive, higher is better. type Options struct { Quality int } // Encode writes the Image m to w in JPEG 4:2:0 baseline format with the given // options. Default parameters are used if a nil *Options is passed. func Encode(w io.Writer, m image.Image, o *Options) (err error) { defer func() { if r := recover(); r != nil { var ok bool err, ok = r.(error) if !ok { err = fmt.Errorf("JPEG error: %v", r) } } }() b := m.Bounds() dx := b.Dx() dy := b.Dy() if dx <= 0 || dy <= 0 { return fmt.Errorf("image with invalid size, dx: %d, dy: %d (both must be > 0)", dx, dy) } quality := 75 if o != nil { quality = o.Quality } cinfoSize := C.size_t(unsafe.Sizeof(C.struct_jpeg_compress_struct{})) cinfo := (*C.struct_jpeg_compress_struct)(C.malloc(cinfoSize)) defer C.free(unsafe.Pointer(cinfo)) cinfoErrSize := C.size_t(unsafe.Sizeof(C.struct_jpeg_error_mgr{})) cinfo.err = (*C.struct_jpeg_error_mgr)(C.malloc(cinfoErrSize)) defer C.free(unsafe.Pointer(cinfo.err)) C.jpeg_std_error(cinfo.err) cinfo.err.error_exit = (*[0]byte)(C.error_panic) memHelper := C.alloc_mem_helper() C.jpeg_CreateCompress(cinfo, C.JPEG_LIB_VERSION, cinfoSize) C.jpeg_mem_dest(cinfo, &memHelper.buf, &memHelper.buf_size) nBytes := dx * 3 // for a line, 3 bytes per pixel cinfo.image_width = C.JDIMENSION(dx) cinfo.image_height = C.JDIMENSION(dy) gray, isGray := m.(*image.Gray) rgba, isRgba := m.(*image.RGBA) cinfo.input_components = 3 cinfo.in_color_space = C.JCS_RGB if isGray { nBytes = dx cinfo.input_components = 1 cinfo.in_color_space = C.JCS_GRAYSCALE } // Note: for more speed could try to go directly to JCS_EXT_RGBA but not // sure if libjpeg matches Go and treats JCS_EXT_RGBA as alpha-premultipled if isRgba { nBytes = dx * 3 cinfo.input_components = 3 cinfo.in_color_space = C.JCS_RGB } C.jpeg_set_defaults(cinfo) C.jpeg_set_quality(cinfo, C.int(quality), C.TRUE) C.jpeg_start_compress(cinfo, C.TRUE) bufBytes := C.malloc(C.size_t(nBytes)) rowPtr := C.JSAMPROW(bufBytes) buf := sliceFromCBytes(bufBytes, nBytes) if isGray { for y := 0; y < dy; y++ { off := y * gray.Stride copy(buf[:], gray.Pix[off:off+nBytes]) C.jpeg_write_scanlines(cinfo, &rowPtr, 1) } } else if isRgba { for y := 0; y < dy; y++ { off := y * rgba.Stride p := rgba.Pix[off:] dstOff := 0 srcOff := 0 for x := 0; x < dx; x++ { buf[dstOff] = p[srcOff] dstOff++ srcOff++ buf[dstOff] = p[srcOff] dstOff++ srcOff++ buf[dstOff] = p[srcOff] dstOff++ srcOff += 2 } C.jpeg_write_scanlines(cinfo, &rowPtr, 1) } } else { for y := 0; y < dy; y++ { off := 0 for x := 0; x < dx; x++ { r, g, b, _ := m.At(x, y).RGBA() buf[off] = byte(r >> 8) off++ buf[off] = byte(g >> 8) off++ buf[off] = byte(b >> 8) off++ } C.jpeg_write_scanlines(cinfo, &rowPtr, 1) } } C.jpeg_finish_compress(cinfo) C.jpeg_destroy_compress(cinfo) outBs := C.GoBytes(unsafe.Pointer(memHelper.buf), C.int(memHelper.buf_size)) w.Write(outBs) C.free(unsafe.Pointer(memHelper.buf)) C.free(bufBytes) C.free(unsafe.Pointer(memHelper)) return nil } golang-github-antonini-golibjpegturbo-0.0~git20141208.c03a2fa/jpeg_common.c000066400000000000000000000005571454570150300262470ustar00rootroot00000000000000#include "_cgo_export.h" void error_panic(j_common_ptr cinfo) { struct { const char *p; } a; char buffer[JMSG_LENGTH_MAX]; (*cinfo->err->format_message) (cinfo, buffer); goPanic(buffer); } typedef struct { unsigned char *buf; unsigned long buf_size; } mem_helper; mem_helper *alloc_mem_helper() { return (mem_helper*) calloc(1,sizeof(mem_helper)); } golang-github-antonini-golibjpegturbo-0.0~git20141208.c03a2fa/jpeg_common.go000066400000000000000000000003321454570150300264210ustar00rootroot00000000000000package golibjpegturbo /* #cgo LDFLAGS: -ljpeg #include #include #include void goPanic(char *); */ import "C" //export goPanic func goPanic(msg *C.char) { panic(C.GoString(msg)) } golang-github-antonini-golibjpegturbo-0.0~git20141208.c03a2fa/stress_test/000077500000000000000000000000001454570150300261615ustar00rootroot00000000000000golang-github-antonini-golibjpegturbo-0.0~git20141208.c03a2fa/stress_test/main.go000066400000000000000000000075341454570150300274450ustar00rootroot00000000000000package main // stress test to validate encoding/decoding doesn't crash or have inconsistent // results due to gc interaction with cgo code. To run: // go run stress_test/main.go -dir= import ( "bytes" "flag" "fmt" "image" "io/ioutil" "os" "os/user" "path/filepath" "reflect" "runtime" "strings" "sync" "sync/atomic" "github.com/kjk/golibjpegturbo" "github.com/kr/fs" ) func panicIfErr(err error) { if err != nil { panic(err.Error()) } } var ( nEncoded int32 nTotalEncodedSize int64 mu sync.Mutex ) type ImageInfo struct { path string data []byte img image.Image encodedData []byte } func encodeLibjpeg(img image.Image) []byte { var buf bytes.Buffer options := &golibjpegturbo.Options{Quality: 90} err := golibjpegturbo.Encode(&buf, img, options) panicIfErr(err) return buf.Bytes() } func pathExists(path string) bool { _, err := os.Stat(path) return err == nil } func userHomeDir() string { // user.Current() returns nil if cross-compiled e.g. on mac for linux if usr, _ := user.Current(); usr != nil { return usr.HomeDir } return os.Getenv("HOME") } func expandTildeInPath(s string) string { if strings.HasPrefix(s, "~") { return userHomeDir() + s[1:] } return s } func isJpegFile(path string) bool { ext := filepath.Ext(path) ext = strings.ToLower(ext) return ext == ".jpg" || ext == ".jpeg" } func validateImgEq(img1, img2 image.Image) { same := reflect.DeepEqual(img1, img2) if !same { panic("decoded image not consistent across runs") } } func decodeEncodeWorker(c chan *ImageInfo) { for ii := range c { d := ii.data r := bytes.NewReader(d) img, err := golibjpegturbo.Decode(r) if err != nil { // we have decoded the image during setup, so this should always succeed panic(fmt.Sprintf("failed to decode %s with %s\n", ii.path, err)) } validateImgEq(ii.img, img) encoded := encodeLibjpeg(img) mu.Lock() if ii.encodedData == nil { ii.encodedData = encoded } mu.Unlock() if !bytes.Equal(encoded, ii.encodedData) { panic("encoded data not consistent across runs") } atomic.AddInt64(&nTotalEncodedSize, int64(len(encoded))) n := atomic.AddInt32(&nEncoded, 1) if n%100 == 0 { fmt.Printf("Decoded/encoded %d images\n", n) } } } func main() { runtime.GOMAXPROCS(runtime.NumCPU()) var flagDir string flag.StringVar(&flagDir, "dir", "", "directory with images") flag.Parse() if flagDir == "" { flag.Usage() os.Exit(2) } dir := expandTildeInPath(flagDir) if !pathExists(dir) { fmt.Printf("dir %s doesn't exist\n", dir) flag.Usage() os.Exit(2) } walker := fs.Walk(dir) var imagePaths []string nMaxImages := 100 for walker.Step() { st := walker.Stat() if !st.Mode().IsRegular() { continue } path := walker.Path() if !isJpegFile(path) { continue } imagePaths = append(imagePaths, path) if len(imagePaths) >= nMaxImages { break } } if len(imagePaths) == 0 { fmt.Printf("There are no jpeg images in %s\n", dir) flag.Usage() os.Exit(2) } var images []*ImageInfo for _, path := range imagePaths { data, err := ioutil.ReadFile(path) if err != nil { fmt.Printf("ioutil.ReadFile() failed with %s\n", err) continue } img, err := golibjpegturbo.DecodeData(data) if err != nil { fmt.Printf("Failed to decode %s with %s\n", path, err) continue } ii := &ImageInfo{ path: path, data: data, img: img, } images = append(images, ii) } fmt.Printf("Read %d images\n", len(images)) c := make(chan *ImageInfo) nWorkers := runtime.NumCPU() - 2 // don't fully overload the machine if nWorkers < 1 { nWorkers = 1 } fmt.Printf("Staring %d workers\n", nWorkers) for i := 0; i < nWorkers; i++ { go decodeEncodeWorker(c) } fmt.Printf("To stop me, use Ctrl-C. Otherwise, I'll just keep going\n") i := 0 for { c <- images[i] i++ i = i % len(images) } }