Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
342 changes: 249 additions & 93 deletions jpeg/compress.go

Large diffs are not rendered by default.

311 changes: 222 additions & 89 deletions jpeg/decompress.go

Large diffs are not rendered by default.

25 changes: 16 additions & 9 deletions jpeg/destinationManager.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ static void free_jpeg_destination_mgr(struct jpeg_destination_mgr *p) {
import "C"

import (
"errors"
"io"
"sync"
"unsafe"
Expand Down Expand Up @@ -61,26 +62,29 @@ func destinationInit(cinfo *C.struct_jpeg_compress_struct) {
// do nothing
}

func flushBuffer(mgr *destinationManager, inBuffer int) {
func flushBuffer(mgr *destinationManager, inBuffer int) error {
wrote := 0
for wrote != inBuffer {
slice := C.GoBytes(unsafe.Pointer(uintptr(mgr.buffer)+uintptr(wrote)), C.int(inBuffer-wrote))
bytes, err := mgr.dest.Write(slice)
if err != nil {
releaseDestinationManager(mgr)
panic(err)
return err
}
wrote += int(bytes)
wrote += bytes
}
mgr.pub.free_in_buffer = writeBufferSize
mgr.pub.next_output_byte = (*C.JOCTET)(mgr.buffer)
return nil
}

//export destinationEmpty
func destinationEmpty(cinfo *C.struct_jpeg_compress_struct) C.boolean {
// need to write *entire* buffer, not subtracting free_in_buffer
mgr := getDestinationManager(cinfo)
flushBuffer(mgr, writeBufferSize)
err := flushBuffer(mgr, writeBufferSize)
if err != nil {
return C.FALSE
}
return C.TRUE
}

Expand All @@ -89,19 +93,22 @@ func destinationTerm(cinfo *C.struct_jpeg_compress_struct) {
// just empty buffer
mgr := getDestinationManager(cinfo)
inBuffer := int(writeBufferSize - mgr.pub.free_in_buffer)
flushBuffer(mgr, inBuffer)
flushBuffer(mgr, inBuffer) // can ignore error here
}

func makeDestinationManager(dest io.Writer, cinfo *C.struct_jpeg_compress_struct) (mgr *destinationManager) {
func makeDestinationManager(dest io.Writer, cinfo *C.struct_jpeg_compress_struct) (mgr *destinationManager, err error) {
mgr = new(destinationManager)
mgr.dest = dest
mgr.pub = C.calloc_jpeg_destination_mgr()
if mgr.pub == nil {
panic("Failed to allocate C.struct_jpeg_destination_mgr")
err = errors.New("failed to allocate C.struct_jpeg_destination_mgr")
return
}
mgr.buffer = C.calloc(writeBufferSize, 1)
if mgr.buffer == nil {
panic("Failed to allocate buffer")
C.free_jpeg_destination_mgr(mgr.pub)
err = errors.New("failed to allocate buffer")
return
}
mgr.pub.init_destination = (*[0]byte)(C.destinationInit)
mgr.pub.empty_output_buffer = (*[0]byte)(C.destinationEmpty)
Expand Down
37 changes: 37 additions & 0 deletions jpeg/error.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package jpeg

/*
#include <stdio.h>
#include <stdlib.h>
#include "jpeglib.h"

static void error_message(j_common_ptr cinfo, char *buf, size_t capa) {
if (cinfo != NULL && cinfo->err != NULL) {
if (cinfo->err->format_message != NULL) {
(*cinfo->err->format_message)(cinfo, buf);
}
else {
snprintf(buf, capa, "JPEG error code %d", cinfo->err->msg_code);
}
}
else {
snprintf(buf, capa, "JPEG unknown error");
}
}
*/
import "C"

import (
"unsafe"
)

func jpegErrorMessage(cinfo unsafe.Pointer) string {
buf := C.calloc(1, C.JMSG_LENGTH_MAX)
if buf == nil {
return "cannot allocate memory"
}
defer C.free(buf)
msg := (*C.char)(buf)
C.error_message(C.j_common_ptr(cinfo), msg, C.JMSG_LENGTH_MAX)
return C.GoString(msg)
}
10 changes: 10 additions & 0 deletions jpeg/error_c.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
#ifndef __INTELLISENSE__
#include "_cgo_export.h"
#endif
#include "jpeg.h"

/* must not return */
void error_longjmp(j_common_ptr cinfo) {
struct my_error_mgr *err = (struct my_error_mgr *)cinfo->err;
longjmp(err->jmpbuf, err->pub.msg_code);
}
19 changes: 19 additions & 0 deletions jpeg/issue51_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package jpeg_test

import (
"bytes"
"testing"

"github.com/pixiv/go-libjpeg/jpeg"
)

var data = []byte("\xff\xd8\xff\xdb\x00C\x000000000000000" +
"00000000000000000000" +
"00000000000000000000" +
"00000000000\xff\xc9\x00\v\b00\x000" +
"\x01\x01\x14\x00\xff\xda\x00\b\x01\x010\x00?\x0000")

// https://github.com/pixiv/go-libjpeg/issues/51
func TestIssue51(t *testing.T) {
jpeg.Decode(bytes.NewReader(data), &jpeg.DecoderOptions{})
}
23 changes: 23 additions & 0 deletions jpeg/jpeg.h
Original file line number Diff line number Diff line change
@@ -1,4 +1,27 @@
#pragma once

#include <stdio.h>
#include <stdlib.h>
#include <setjmp.h>
#include "jpeglib.h"
#include "jerror.h"

// the dimension multiple to which data buffers should be aligned.
#define ALIGN_SIZE 16

struct my_error_mgr {
struct jpeg_error_mgr pub;
jmp_buf jmpbuf;
};

#if defined(_WIN32) && !defined(__CYGWIN__)
// setjmp/longjmp occasionally crashes on Windows
// see https://github.com/golang/go/issues/13672
// use __builtin_setjmp/longjmp for workaround
#undef setjmp
#define setjmp(b) __builtin_setjmp(b)
#undef longjmp
#define longjmp(b, c) __builtin_longjmp((b), 1) // __builtin_longjmp accepts only `1` as the secound argument
#endif

void error_longjmp(j_common_ptr cinfo);
2 changes: 1 addition & 1 deletion jpeg/jpeg_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -227,7 +227,7 @@ func TestDecodeAndEncode(t *testing.T) {
}()

if err := jpeg.Encode(w, img, &jpeg.EncoderOptions{Quality: 90}); err != nil {
t.Errorf("Encode returns error: %v", err)
t.Errorf("%s: Encode returns error: %v", file, err)
}
}
}
Expand Down
39 changes: 25 additions & 14 deletions jpeg/sourceManager.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,11 +37,22 @@ static void free_jpeg_source_mgr(struct jpeg_source_mgr *p) {
import "C"

import (
"errors"
"io"
"reflect"
"sync"
"unsafe"
)

func makePseudoSlice(ptr unsafe.Pointer) []byte {
var buffer []byte
slice := (*reflect.SliceHeader)(unsafe.Pointer(&buffer))
slice.Cap = readBufferSize
slice.Len = readBufferSize
slice.Data = uintptr(ptr)
return buffer
}

const readBufferSize = 16384

var sourceManagerMapMutex sync.RWMutex
Expand Down Expand Up @@ -78,7 +89,9 @@ func sourceSkip(dinfo *C.struct_jpeg_decompress_struct, bytes C.long) {
if bytes > 0 {
for bytes >= C.long(mgr.pub.bytes_in_buffer) {
bytes -= C.long(mgr.pub.bytes_in_buffer)
sourceFill(dinfo)
if sourceFill(dinfo) != C.TRUE {
break
}
}
}
mgr.pub.bytes_in_buffer -= C.size_t(bytes)
Expand All @@ -96,42 +109,40 @@ func sourceTerm(dinfo *C.struct_jpeg_decompress_struct) {
//export sourceFill
func sourceFill(dinfo *C.struct_jpeg_decompress_struct) C.boolean {
mgr := getSourceManager(dinfo)
buffer := [readBufferSize]byte{}
bytes, err := mgr.src.Read(buffer[:])
C.memcpy(mgr.buffer, unsafe.Pointer(&buffer[0]), C.size_t(bytes))
buffer := makePseudoSlice(mgr.buffer)
bytes, err := mgr.src.Read(buffer)
mgr.pub.bytes_in_buffer = C.size_t(bytes)
mgr.currentSize = bytes
mgr.pub.next_input_byte = (*C.JOCTET)(mgr.buffer)
if err == io.EOF {
if bytes == 0 {
if mgr.startOfFile {
releaseSourceManager(mgr)
panic("input is empty")
return C.FALSE
}
// EOF and need more data. Fill in a fake EOI to get a partial image.
footer := []byte{0xff, C.JPEG_EOI}
C.memcpy(mgr.buffer, unsafe.Pointer(&footer[0]), C.size_t(len(footer)))
mgr.pub.bytes_in_buffer = 2
mgr.pub.bytes_in_buffer = C.size_t(copy(buffer, []byte{0xff, C.JPEG_EOI}))
}
} else if err != nil {
releaseSourceManager(mgr)
panic(err)
return C.FALSE
}
mgr.startOfFile = false

return C.TRUE
}

func makeSourceManager(src io.Reader, dinfo *C.struct_jpeg_decompress_struct) (mgr *sourceManager) {
func makeSourceManager(src io.Reader, dinfo *C.struct_jpeg_decompress_struct) (mgr *sourceManager, err error) {
mgr = new(sourceManager)
mgr.src = src
mgr.pub = C.calloc_jpeg_source_mgr()
if mgr.pub == nil {
panic("Failed to allocate C.struct_jpeg_source_mgr")
err = errors.New("failed to allocate C.struct_jpeg_source_mgr")
return
}
mgr.buffer = C.calloc(readBufferSize, 1)
if mgr.buffer == nil {
panic("Failed to allocate buffer")
C.free_jpeg_source_mgr(mgr.pub)
err = errors.New("failed to allocate buffer")
return
}
mgr.pub.init_source = (*[0]byte)(C.sourceInit)
mgr.pub.fill_input_buffer = (*[0]byte)(C.sourceFill)
Expand Down