Skip to content

Commit

Permalink
Merge branch 'davidbyttow:master' into optimize-icc-16bit-fix
Browse files Browse the repository at this point in the history
  • Loading branch information
Elad-Laufer authored Dec 6, 2021
2 parents 805153a + 56a7f51 commit d0c679d
Show file tree
Hide file tree
Showing 9 changed files with 165 additions and 9 deletions.
1 change: 1 addition & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ jobs:
sudo add-apt-repository -y ppa:strukturag/libde265
sudo add-apt-repository -y ppa:strukturag/libheif
sudo add-apt-repository -y ppa:tonimelisma/ppa
sudo apt-get -y install libopenjp2-7
sudo apt-get -y install libvips-dev
- name: Install macos deps
Expand Down
3 changes: 2 additions & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@ The CI/CD currently checks strict format and linting, so be sure to add comments

## Run the tests

```make test
```bash
$ make test
```

## Get in touch
Expand Down
Binary file added resources/jp2k-orientation-6.jp2
Binary file not shown.
37 changes: 36 additions & 1 deletion vips/foreign.c
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,13 @@ int load_image_buffer(LoadParams *params, void *buf, size_t len,
code = vips_heifload_buffer(buf, len, out, "page", params->page, "n",
params->n, "thumbnail", params->heifThumbnail,
"autorotate", params->autorotate, NULL);

}
#if (VIPS_MAJOR_VERSION >= 8) && (VIPS_MINOR_VERSION >= 11)
else if (imageType == JP2K) {
code = vips_jp2kload_buffer(buf, len, out, "page", params->page, NULL);
}
#endif

return code;
}
Expand Down Expand Up @@ -137,6 +143,11 @@ int set_heifload_options(VipsOperation *operation, LoadParams *params) {
return 0;
}

int set_jp2kload_options(VipsOperation *operation, LoadParams *params) {
MAYBE_SET_INT(operation, params->page, "page");
return 0;
}

int set_magickload_options(VipsOperation *operation, LoadParams *params) {
MAYBE_SET_INT(operation, params->page, "page");
MAYBE_SET_INT(operation, params->n, "n");
Expand Down Expand Up @@ -266,6 +277,7 @@ int set_webpsave_options(VipsOperation *operation, SaveParams *params) {
vips_object_set(VIPS_OBJECT(operation),
"strip", params->stripMetadata,
"lossless", params->webpLossless,
"near_lossless", params->webpNearLossless,
"reduction_effort", params->webpReductionEffort,
"profile", params->webpIccProfile ? params->webpIccProfile : "none",
NULL);
Expand Down Expand Up @@ -327,6 +339,19 @@ int set_avifsave_options(VipsOperation *operation, SaveParams *params) {
return ret;
}

int set_jp2ksave_options(VipsOperation *operation, SaveParams *params) {
int ret = vips_object_set(
VIPS_OBJECT(operation), "subsample_mode", params->jpegSubsample,
"tile_height", params->jp2kTileHeight, "tile_width", params->jp2kTileWidth,
"lossless", params->jp2kLossless, NULL);

if (!ret && params->quality) {
ret = vips_object_set(VIPS_OBJECT(operation), "Q", params->quality, NULL);
}

return ret;
}

int load_from_buffer(LoadParams *params, void *buf, size_t len) {
switch (params->inputFormat) {
case JPEG:
Expand Down Expand Up @@ -359,6 +384,9 @@ int load_from_buffer(LoadParams *params, void *buf, size_t len) {
case AVIF:
return load_buffer("heifload_buffer", buf, len, params,
set_heifload_options);
case JP2K:
return load_buffer("jp2kload_buffer", buf, len, params,
set_jp2kload_options);
default:
g_warning("Unsupported input type given: %d", params->inputFormat);
}
Expand All @@ -381,6 +409,8 @@ int save_to_buffer(SaveParams *params) {
return save_buffer("magicksave_buffer", params, set_magicksave_options);
case AVIF:
return save_buffer("heifsave_buffer", params, set_avifsave_options);
case JP2K:
return save_buffer("jp2ksave_buffer", params, set_jp2ksave_options);
default:
g_warning("Unsupported output type given: %d", params->outputFormat);
}
Expand Down Expand Up @@ -431,6 +461,7 @@ static SaveParams defaultSaveParams = {
.pngFilter = VIPS_FOREIGN_PNG_FILTER_NONE,

.webpLossless = FALSE,
.webpNearLossless = FALSE,
.webpReductionEffort = 4,
.webpIccProfile = NULL,

Expand All @@ -445,7 +476,11 @@ static SaveParams defaultSaveParams = {
.tiffXRes = 1.0,
.tiffYRes = 1.0,

.avifSpeed = 5};
.avifSpeed = 5,

.jp2kLossless = FALSE,
.jp2kTileHeight = 512,
.jp2kTileWidth = 512};

SaveParams create_save_params(ImageType outputFormat) {
SaveParams params = defaultSaveParams;
Expand Down
29 changes: 29 additions & 0 deletions vips/foreign.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ const (
ImageTypeHEIF ImageType = C.HEIF
ImageTypeBMP ImageType = C.BMP
ImageTypeAVIF ImageType = C.AVIF
ImageTypeJP2K ImageType = C.JP2K
)

var imageTypeExtensionMap = map[ImageType]string{
Expand All @@ -57,6 +58,7 @@ var imageTypeExtensionMap = map[ImageType]string{
ImageTypeHEIF: ".heic",
ImageTypeBMP: ".bmp",
ImageTypeAVIF: ".avif",
ImageTypeJP2K: ".jp2",
}

// ImageTypes defines the various image types supported by govips
Expand All @@ -72,6 +74,7 @@ var ImageTypes = map[ImageType]string{
ImageTypeHEIF: "heif",
ImageTypeBMP: "bmp",
ImageTypeAVIF: "heif",
ImageTypeJP2K: "jp2k",
}

// TiffCompression represents method for compressing a tiff at export
Expand Down Expand Up @@ -138,6 +141,8 @@ func DetermineImageType(buf []byte) ImageType {
return ImageTypePDF
} else if isBMP(buf) {
return ImageTypeBMP
} else if isJP2K(buf) {
return ImageTypeJP2K
} else {
return ImageTypeUnknown
}
Expand Down Expand Up @@ -225,6 +230,14 @@ func isBMP(buf []byte) bool {
return bytes.HasPrefix(buf, bmpHeader)
}

//X'0000 000C 6A50 2020 0D0A 870A'
var jp2kHeader = []byte("\x00\x00\x00\x0C\x6A\x50\x20\x20\x0D\x0A\x87\x0A")

// https://datatracker.ietf.org/doc/html/rfc3745
func isJP2K(buf []byte) bool {
return bytes.HasPrefix(buf, jp2kHeader)
}

func vipsLoadFromBuffer(buf []byte, params *ImportParams) (*C.VipsImage, ImageType, error) {
src := buf
// Reference src here so it's not garbage collected during image initialization.
Expand Down Expand Up @@ -346,6 +359,7 @@ func vipsSaveWebPToBuffer(in *C.VipsImage, params WebpExportParams) ([]byte, err
p.stripMetadata = C.int(boolToInt(params.StripMetadata))
p.quality = C.int(params.Quality)
p.webpLossless = C.int(boolToInt(params.Lossless))
p.webpNearLossless = C.int(boolToInt(params.NearLossless))
p.webpReductionEffort = C.int(params.ReductionEffort)

if params.IccProfile != "" {
Expand Down Expand Up @@ -393,6 +407,21 @@ func vipsSaveAVIFToBuffer(in *C.VipsImage, params AvifExportParams) ([]byte, err
return vipsSaveToBuffer(p)
}

func vipsSaveJP2KToBuffer(in *C.VipsImage, params Jp2kExportParams) ([]byte, error) {
incOpCounter("save_jp2k_buffer")

p := C.create_save_params(C.JP2K)
p.inputImage = in
p.outputFormat = C.JP2K
p.quality = C.int(params.Quality)
p.jp2kLossless = C.int(boolToInt(params.Lossless))
p.jp2kTileWidth = C.int(params.TileWidth)
p.jp2kTileHeight = C.int(params.TileHeight)
p.jpegSubsample = C.VipsForeignJpegSubsample(params.SubsampleMode)

return vipsSaveToBuffer(p)
}

func vipsSaveGIFToBuffer(in *C.VipsImage, params GifExportParams) ([]byte, error) {
incOpCounter("save_gif_buffer")

Expand Down
9 changes: 8 additions & 1 deletion vips/foreign.h
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,8 @@ typedef enum types {
MAGICK,
HEIF,
BMP,
AVIF
AVIF,
JP2K
} ImageType;

typedef enum ParamType {
Expand Down Expand Up @@ -97,6 +98,7 @@ typedef struct SaveParams {

// WEBP
BOOL webpLossless;
BOOL webpNearLossless;
int webpReductionEffort;
char *webpIccProfile;

Expand All @@ -115,6 +117,11 @@ typedef struct SaveParams {

// AVIF
int avifSpeed;

// JPEG2000
BOOL jp2kLossless;
int jp2kTileWidth;
int jp2kTileHeight;
} SaveParams;

SaveParams create_save_params(ImageType outputFormat);
Expand Down
11 changes: 11 additions & 0 deletions vips/foreign_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -127,3 +127,14 @@ func Test_DetermineImageType__AVIF(t *testing.T) {
imageType := DetermineImageType(buf)
assert.Equal(t, ImageTypeAVIF, imageType)
}

func Test_DetermineImageType__JP2K(t *testing.T) {
Startup(&Config{})

buf, err := ioutil.ReadFile(resources + "jp2k-orientation-6.jp2")
assert.NoError(t, err)
assert.NotNil(t, buf)

imageType := DetermineImageType(buf)
assert.Equal(t, ImageTypeJP2K, imageType)
}
49 changes: 43 additions & 6 deletions vips/image.go
Original file line number Diff line number Diff line change
Expand Up @@ -233,6 +233,7 @@ type WebpExportParams struct {
StripMetadata bool
Quality int
Lossless bool
NearLossless bool
ReductionEffort int
IccProfile string
}
Expand All @@ -243,6 +244,7 @@ func NewWebpExportParams() *WebpExportParams {
return &WebpExportParams{
Quality: 75,
Lossless: false,
NearLossless: false,
ReductionEffort: 4,
}
}
Expand Down Expand Up @@ -307,6 +309,25 @@ func NewAvifExportParams() *AvifExportParams {
}
}

// Jp2kExportParams are options when exporting an JPEG2000 to file or buffer.
type Jp2kExportParams struct {
Quality int
Lossless bool
TileWidth int
TileHeight int
SubsampleMode SubsampleMode
}

// NewJp2kExportParams creates default values for an export of an JPEG2000 image.
func NewJp2kExportParams() *Jp2kExportParams {
return &Jp2kExportParams{
Quality: 80,
Lossless: false,
TileWidth: 512,
TileHeight: 512,
}
}

// NewImageFromReader loads an ImageRef from the given reader
func NewImageFromReader(r io.Reader) (*ImageRef, error) {
buf, err := ioutil.ReadAll(r)
Expand Down Expand Up @@ -341,12 +362,12 @@ func LoadImageFromBuffer(buf []byte, params *ImportParams) (*ImageRef, error) {
params = NewImportParams()
}

image, format, err := vipsLoadFromBuffer(buf, params)
vipsImage, format, err := vipsLoadFromBuffer(buf, params)
if err != nil {
return nil, err
}

ref := newImageRef(image, format, buf)
ref := newImageRef(vipsImage, format, buf)

govipsLog("govips", LogLevelDebug, fmt.Sprintf("created imageref %p", ref))
return ref, nil
Expand Down Expand Up @@ -640,6 +661,8 @@ func (r *ImageRef) ExportNative() ([]byte, *ImageMetadata, error) {
return r.ExportTiff(NewTiffExportParams())
case ImageTypeAVIF:
return r.ExportAvif(NewAvifExportParams())
case ImageTypeJP2K:
return r.ExportJp2k(NewJp2kExportParams())
default:
return r.ExportJpeg(NewJpegExportParams())
}
Expand Down Expand Up @@ -746,6 +769,20 @@ func (r *ImageRef) ExportAvif(params *AvifExportParams) ([]byte, *ImageMetadata,
return buf, r.newMetadata(ImageTypeAVIF), nil
}

// ExportJp2k exports the image as JPEG2000 to a buffer.
func (r *ImageRef) ExportJp2k(params *Jp2kExportParams) ([]byte, *ImageMetadata, error) {
if params == nil {
params = NewJp2kExportParams()
}

buf, err := vipsSaveJP2KToBuffer(r.image, *params)
if err != nil {
return nil, nil, err
}

return buf, r.newMetadata(ImageTypeJP2K), nil
}

// CompositeMulti composites the given overlay image on top of the associated image with provided blending mode.
func (r *ImageRef) CompositeMulti(ins []*ImageComposite) error {
out, err := vipsComposite(toVipsCompositeStructs(r, ins))
Expand Down Expand Up @@ -834,8 +871,8 @@ func (r *ImageRef) ExtractBand(band int, num int) error {
// BandJoin joins a set of images together, bandwise.
func (r *ImageRef) BandJoin(images ...*ImageRef) error {
vipsImages := []*C.VipsImage{r.image}
for _, image := range images {
vipsImages = append(vipsImages, image.image)
for _, vipsImage := range images {
vipsImages = append(vipsImages, vipsImage.image)
}

out, err := vipsBandJoin(vipsImages)
Expand Down Expand Up @@ -1383,8 +1420,8 @@ func (r *ImageRef) ToBytes() ([]byte, error) {
}
defer C.free(cData)

bytes := C.GoBytes(unsafe.Pointer(cData), C.int(cSize))
return bytes, nil
data := C.GoBytes(unsafe.Pointer(cData), C.int(cSize))
return data, nil
}

func (r *ImageRef) determineInputICCProfile() (inputProfile string) {
Expand Down
35 changes: 35 additions & 0 deletions vips/image_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,23 @@ func TestImageRef_WebP__ReducedEffort(t *testing.T) {
assert.NoError(t, err)
}

func TestImageRef_WebP__NearLossless(t *testing.T) {
Startup(nil)

srcBytes, err := ioutil.ReadFile(resources + "webp+alpha.webp")
require.NoError(t, err)

src := bytes.NewReader(srcBytes)
img, err := NewImageFromReader(src)
require.NoError(t, err)
require.NotNil(t, img)

params := NewWebpExportParams()
params.NearLossless = true
_, _, err = img.ExportWebp(params)
assert.NoError(t, err)
}

func TestImageRef_PNG(t *testing.T) {
Startup(nil)

Expand Down Expand Up @@ -924,6 +941,24 @@ func TestImageRef_AVIF(t *testing.T) {
assert.Equal(t, ImageTypeAVIF, metadata.Format)
}

func TestImageRef_JP2K(t *testing.T) {
if MajorVersion == 8 && MinorVersion < 11 {
t.Skip("JPEG2000 is only supported in vips 8.11+")
}
Startup(nil)

raw, err := ioutil.ReadFile(resources + "jp2k-orientation-6.jp2")
require.NoError(t, err)

img, err := NewImageFromBuffer(raw)
require.NoError(t, err)
require.NotNil(t, img)

_, metadata, err := img.ExportJp2k(nil)
assert.NoError(t, err)
assert.Equal(t, ImageTypeJP2K, metadata.Format)
}

// TODO unit tests to cover:
// NewImageFromReader failing test
// NewImageFromFile failing test
Expand Down

0 comments on commit d0c679d

Please sign in to comment.