-
-
Notifications
You must be signed in to change notification settings - Fork 1.3k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Added tiff compression option #738
Conversation
My guess is that I need to change this line to support the options, but am not super sure about the proper syntax: Line 845 in 72f69dd
|
Okay I mucked around a bit and it looks like this is working now. Still not exactly sure how to test the compression is actually compressing... Also not entirely sure how to tiff predictor works, but it does seem to help significantly in reducing file size. Multiple .TIF files around 27MB are reduced to ~20MB without predictor and ~10MB with horizontal predictor. |
Hello, thanks for this PR, a quick glance suggests it's a very good start. I'll make some time in the next day or so to take a more detailed look. |
lib/output.js
Outdated
@@ -211,6 +211,8 @@ const webp = function webp (options) { | |||
* @param {Object} [options] - output options | |||
* @param {Number} [options.quality=80] - quality, integer 1-100 | |||
* @param {Boolean} [options.force=true] - force TIFF output, otherwise attempt to use input format | |||
* @param {Boolean} [options.compression='jpeg'] - compression options: lzw, deflate, jpeg | |||
* @param {Boolean} [options.compression='jpeg'] - compression predictor options: none, horizontal, float |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Didi you mean options.predictor
here?
src/pipeline.cc
Outdated
@@ -1199,6 +1200,25 @@ NAN_METHOD(pipeline) { | |||
baton->webpLossless = AttrTo<bool>(options, "webpLossless"); | |||
baton->webpNearLossless = AttrTo<bool>(options, "webpNearLossless"); | |||
baton->tiffQuality = AttrTo<uint32_t>(options, "tiffQuality"); | |||
// tiff compression options | |||
std::string tiffCompression = AttrAsStr(options, "tiffCompression"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Given we're going for a string-based input here, can we use vips_enum_from_nick
for compression
and predictor
? (like the kernel property of the resize operation.)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Okay yea this looks like a better idea. Could you show me where I define the map from nickname -> type though? I can't seem to find it for the example you gave, so I'm wondering how the function takes the string input and decides which type to use?
test/unit/io.js
Outdated
@@ -861,6 +861,54 @@ describe('Input/output', function () { | |||
}); | |||
}); | |||
|
|||
it('TIFF lzw compression does not throw error', function () { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for adding some failure scenarios to the tests. Looking forward to seeing some more positive scenarios/results too.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could you suggest a strategy for the positive tests? I don't want to simply test that the file got smaller, since occasionally that doesn't work (16 bit tiff files with zip lzw have issues sometimes). Is there a way to read from the metadata that a tiff is compressed?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't want to simply test that the file got smaller, since occasionally that doesn't work
Ah, that's along the lines of what I was thinking. Feel free to add a test fixture that can be relied upon to do this. I guess we can at least verify that the TIFF file(s) can be re-opened.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I guess we can test that the file got smaller for the testing image, I'll add that.
I added a positive test, but not sure it works at the moment. Need to fix the vips_enum_from_nick thing in order to get this working again. Let me know how to use that function and I'll get it set up. Thanks! |
src/pipeline.cc
Outdated
@@ -1199,6 +1200,27 @@ NAN_METHOD(pipeline) { | |||
baton->webpLossless = AttrTo<bool>(options, "webpLossless"); | |||
baton->webpNearLossless = AttrTo<bool>(options, "webpNearLossless"); | |||
baton->tiffQuality = AttrTo<uint32_t>(options, "tiffQuality"); | |||
// tiff compression options | |||
VipsForeignTiffCompression tiffCompression = static_cast<VipsForeignTiffCompression>( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The following might work:
baton->tiffCompression = static_cast<VipsForeignTiffCompression>(
vips_enum_from_nick(nullptr, VIPS_TYPE_FOREIGN_TIFF_COMPRESSION,
AttrAsStr(options, "tiffCompression")));
Okay I added the positive test and started using |
I think you'll need to add the default values for |
yarn.lock
Outdated
@@ -0,0 +1,4467 @@ | |||
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Was the inclusion of this file intentional?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, no it wasn't. I'll remove that.
test/fixtures/index.js
Outdated
@@ -84,6 +84,7 @@ module.exports = { | |||
inputWebPWithTransparency: getPath('5_webp_a.webp'), // http://www.gstatic.com/webp/gallery3/5_webp_a.webp | |||
inputTiff: getPath('G31D.TIF'), // http://www.fileformat.info/format/tiff/sample/e6c9a6e5253348f4aef6d17b534360ab/index.htm | |||
inputTiffCielab: getPath('cielab-dagams.tiff'), // https://github.com/lovell/sharp/issues/646 | |||
inputTiffUncompressed: getPath('uncompressed_tiff.tiff'), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What are the origins/rights etc. of this image?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just a photo I took and have been using for testing myself. Would you prefer a different?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As long as you're happy to licence it, perhaps using CC0, that's OK.
If possible, please could you use a smaller file, or perhaps only a portion of this file, as this is 15MB!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not sure exactly what that would mean. Do you have a place you suggest I look for a licensed tiff?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
https://code.google.com/archive/p/imagetestsuite/wikis/TIFFTestSuite.wiki includes 20 images tagged as "Compression Scheme: None" so might provide something suitable. Your own image is fine as long as you grant us permission to distribute it :)
test/unit/io.js
Outdated
it('TIFF lzw compression shrinks test file', function (done) { | ||
let startSize; | ||
sharp(fixtures.inputUncompressedTiff) | ||
.toBuffer((err, buffer, info) => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Only one toBuffer
is supported but there are two here. Perhaps use fs.statSync to read startSize
.
test/unit/io.js
Outdated
@@ -863,11 +863,11 @@ describe('Input/output', function () { | |||
|
|||
it('TIFF lzw compression shrinks test file', function (done) { | |||
let startSize; | |||
fs.statSync(fixtures.inputUncompressedTiff, (err, stats) => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Did you mean:
const startSize = fs.statSync(fixtures.inputUncompressedTiff).size;
test/unit/io.js
Outdated
@@ -861,6 +861,70 @@ describe('Input/output', function () { | |||
}); | |||
}); | |||
|
|||
it('TIFF lzw compression shrinks test file', function (done) { | |||
const startSize = fs.statSync(fixtures.inputUncompressedTiff).size; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The variable that you created is called inputTiffUncompressed
not inputUncompressedTiff
.
Please, please run the tests locally and ensure they are passing before pushing further code changes to Github.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have it working on my end, so I was only getting up on a Saturday to make the tests work to enrich your library. I apologize if it annoyed you.
Feel free to fix it if you want it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you. I hope you understand that PRs where tests are failing cannot be merged and code that is untested cannot be safely maintained, refactored or otherwise improved.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've just re-read my original comment and it makes me sound so much more annoyed than the slight level of frustration I was experiencing at that point. I'm sorry for any ill feelings it caused to you. Perhaps some form of pre-push hook to run the tests might help here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's alright, sorry I got frustrated as well. It's my second ever PR and I just wasn't thinking about the courtesy and notifications you were probably getting. I'll try to get the tests working and then push it. Thanks for the great lib.
Alright I have run tests on my machine 😏 and added positive tests for other compression options. All tests are passing at this point. I also switched out the uncompressed tiff file for one from the google library you recommended, as I just didn't want to deal with licensing mine. The one strange thing I am seeing is that compressing a tiff with the |
1 similar comment
This is fantastic Kristo, thank you for persisting with this PR, it will make a great addition to sharp. I was able to reproduce the "float" predictor problem using the vips command line tools so let's report this upstream. |
Thanks for putting up with me : ) |
Running
Running the following at the command line works so libvips is OK. vips cast uncompressed_tiff.tiff out.tiff[compression=deflate,predictor=float] float So we'll need to modify sharp to cast pixel values to float when using predictor=float. I'll fix this :) |
TIFF float predictor support added in 088d36b, thanks again! |
I didn't see a work-in-progress branch so I am sending this to master for now. It doesn't introduce any breaking change in api in any case.
It is actually not totally working yet, and I'm wondering if you can help me identify why. My guess is that I'm setting the wrong option here:
this.options.tiffCompression = options.compression;
, as I just followed the pattern of similar functions. Where can I find the correct property name to set, and in what format does the input option need to be? I followed what I had used through the vips CLI:'lzw'
,'deflate'
, and'jpeg'
.Additionally, I'm not exactly sure what to assert for in a test to determine if this is working? I would assume the compression info must be encoded somewhere, but don't see that as accessible through vips.
If you can help me out with what I am doing wrong, and a testing strategy I will update the PR and add documentation for the feature too. Thanks!!