diff --git a/README.md b/README.md index 0bf572d..d677669 100644 --- a/README.md +++ b/README.md @@ -65,7 +65,7 @@ Get size (width and height) from webp image: WebP.webp_size(File.open(filename, "rb").read) => [2000, 2353] -### Encode png, jpg or tiff image to WebP image +### Encode WebP image Encode png, jpg or tiff image to webp: @@ -73,7 +73,7 @@ Encode png, jpg or tiff image to webp: out_filename = File.expand_path(File.join(File.dirname(__FILE__), "tmp/4.webp")) WebP.encode(filename, out_filename) -Encode png, jpg or tiff image to webp (with options): +Encode png, jpg or tiff image to webp with options: WebP.encode(filename, out_filename, quality: 50, resize_w: 100, resize_h: 200) WebP.encode(filename, out_filename, quality: 75, crop_x: 0, cropt_y: 0, crop_w: 100, crop_h: 100) @@ -103,13 +103,34 @@ Possible encode options: * **crop\_x** (int), **crop\_y** (int), **crop\_w** (int), **crop\_h** (int) - crop picture with the given rectangle * **resize\_w** (int), **resize\_h** (int) - resize picture (after any cropping) -### Decode WebP image to png image +### Decode WebP image -Decode webp image to png: +Decode webp image (default format is png): filename = File.expand_path(File.join(File.dirname(__FILE__), "spec/factories/4.webp")) out_filename = File.expand_path(File.join(File.dirname(__FILE__), "tmp/4.png")) WebP.decode(filename, out_filename) + +Decode webp image to pam, ppm or pgm format of image: + + filename = File.expand_path(File.join(File.dirname(__FILE__), "spec/factories/4.webp")) + out_filename = File.expand_path(File.join(File.dirname(__FILE__), "tmp/4.png")) + WebP.decode(filename, out_filename, output_format: :pam) + WebP.decode(filename, out_filename, output_format: :ppm) + WebP.decode(filename, out_filename, output_format: :pgm) + +Decode webp image with options: + + WebP.encode(filename, out_filename, resize_w: 100, resize_h: 200) + WebP.encode(filename, out_filename, crop_x: 0, cropt_y: 0, crop_w: 100, crop_h: 100) + +Possible decode options: + + * **bypass\_filtering** (bool) - disable in-loop filtering + * **no\_fancy\_upsampling** (bool) - don't use the fancy YUV420 upscaler + * **use\_threads** (bool) - use multi-threading + * **crop\_x** (int), **crop\_y** (int), **crop\_w** (int), **crop\_h** (int) - crop picture with the given rectangle + * **resize\_w** (int), **resize\_h** (int) - resize picture (after any cropping) ## Contributing diff --git a/ext/webp_ffi/util.c b/ext/webp_ffi/util.c index e6aec8b..c42457e 100644 --- a/ext/webp_ffi/util.c +++ b/ext/webp_ffi/util.c @@ -280,6 +280,52 @@ static int UtilWritePPM(FILE* fout, const WebPDecBuffer* const buffer, int alpha return 1; } +static int UtilWriteAlphaPlane(FILE* fout, const WebPDecBuffer* const buffer) { + const uint32_t width = buffer->width; + const uint32_t height = buffer->height; + const unsigned char* const a = buffer->u.YUVA.a; + const int a_stride = buffer->u.YUVA.a_stride; + uint32_t y; + assert(a != NULL); + fprintf(fout, "P5\n%d %d\n255\n", width, height); + for (y = 0; y < height; ++y) { + if (fwrite(a + y * a_stride, width, 1, fout) != 1) { + return 0; + } + } + return 1; +} + +static int UtilWritePGM(FILE* fout, const WebPDecBuffer* const buffer) { + const int width = buffer->width; + const int height = buffer->height; + const WebPYUVABuffer* const yuv = &buffer->u.YUVA; + // Save a grayscale PGM file using the IMC4 layout + // (http://www.fourcc.org/yuv.php#IMC4). This is a very + // convenient format for viewing the samples, esp. for + // odd dimensions. + int ok = 1; + int y; + const int uv_width = (width + 1) / 2; + const int uv_height = (height + 1) / 2; + const int out_stride = (width + 1) & ~1; + const int a_height = yuv->a ? height : 0; + fprintf(fout, "P5\n%d %d\n255\n", out_stride, height + uv_height + a_height); + for (y = 0; ok && y < height; ++y) { + ok &= (fwrite(yuv->y + y * yuv->y_stride, width, 1, fout) == 1); + if (width & 1) fputc(0, fout); // padding byte + } + for (y = 0; ok && y < uv_height; ++y) { + ok &= (fwrite(yuv->u + y * yuv->u_stride, uv_width, 1, fout) == 1); + ok &= (fwrite(yuv->v + y * yuv->v_stride, uv_width, 1, fout) == 1); + } + for (y = 0; ok && y < a_height; ++y) { + ok &= (fwrite(yuv->a + y * yuv->a_stride, width, 1, fout) == 1); + if (width & 1) fputc(0, fout); // padding byte + } + return ok; +} + static int UtilReadTIFF(const char* const filename, WebPPicture* const pic, int keep_alpha) { TIFF* const tif = TIFFOpen(filename, "r"); @@ -397,14 +443,12 @@ int UtilSaveOutput(const WebPDecBuffer* const buffer, ok &= UtilWritePPM(fout, buffer, 1); } else if (format == PPM) { ok &= UtilWritePPM(fout, buffer, 0); - } - /* } else if (format == PGM) { - ok &= WritePGM(fout, buffer); + ok &= UtilWritePGM(fout, buffer); } else if (format == ALPHA_PLANE_ONLY) { - ok &= WriteAlphaPlane(fout, buffer); + ok &= UtilWriteAlphaPlane(fout, buffer); } - */ + if (fout) { fclose(fout); } diff --git a/ext/webp_ffi/webp_ffi.c b/ext/webp_ffi/webp_ffi.c index d6a3705..2dc686e 100644 --- a/ext/webp_ffi/webp_ffi.c +++ b/ext/webp_ffi/webp_ffi.c @@ -195,13 +195,34 @@ int webp_decode(const char *in_file, const char *out_file, const FfiWebpDecodeCo OutputFileFormat format = PNG; if (!WebPInitDecoderConfig(&config)) { - fprintf(stderr, "Library version mismatch!\n"); + //fprintf(stderr, "Library version mismatch!\n"); return 1; } if (decode_config->output_format != format){ format = decode_config->output_format; } + if (decode_config->no_fancy_upsampling > 0){ + config.options.no_fancy_upsampling = 1; + } + if (decode_config->bypass_filtering > 0){ + config.options.bypass_filtering = 1; + } + if (decode_config->use_threads > 0){ + config.options.use_threads = 1; + } + if ((decode_config->crop_w | decode_config->crop_h) > 0){ + config.options.use_cropping = 1; + config.options.crop_left = decode_config->crop_x; + config.options.crop_top = decode_config->crop_y; + config.options.crop_width = decode_config->crop_w; + config.options.crop_height = decode_config->crop_h; + } + if ((decode_config->resize_w | decode_config->resize_h) > 0){ + config.options.use_scaling = 1; + config.options.scaled_width = decode_config->resize_w; + config.options.scaled_height = decode_config->resize_h; + } VP8StatusCode status = VP8_STATUS_OK; size_t data_size = 0; @@ -211,7 +232,7 @@ int webp_decode(const char *in_file, const char *out_file, const FfiWebpDecodeCo status = WebPGetFeatures(data, data_size, bitstream); if (status != VP8_STATUS_OK) { - fprintf(stderr, "This is invalid webp image!\n"); + //fprintf(stderr, "This is invalid webp image!\n"); return_value = 2; goto Error; } @@ -239,7 +260,7 @@ int webp_decode(const char *in_file, const char *out_file, const FfiWebpDecodeCo status = WebPDecode(data, data_size, &config); if (status != VP8_STATUS_OK) { - fprintf(stderr, "Decoding of %s failed.\n", in_file); + //fprintf(stderr, "Decoding of %s failed.\n", in_file); return_value = 4; goto Error; } diff --git a/lib/webp/error.rb b/lib/webp/error.rb index a19b47d..992465c 100644 --- a/lib/webp/error.rb +++ b/lib/webp/error.rb @@ -10,7 +10,10 @@ module WebP "Cannot encode picture as WebP"] DECODER_ERRORS = [ - ""] + "Version mismatch", + "Invalid webp image", + "Invalid output format", + "Decoding failed"] class InvalidImageFormatError < StandardError; end class EncoderError < StandardError; end diff --git a/lib/webp/options.rb b/lib/webp/options.rb index abed806..3ca17fa 100644 --- a/lib/webp/options.rb +++ b/lib/webp/options.rb @@ -31,6 +31,13 @@ def decode_pointer if @user_options[:output_format] && [:png, :pam, :ppm, :pgm, :alpha_plane_only].include?(@user_options[:output_format]) options_struct[:output_format] = C::OutputFileFormat[@user_options[:output_format]] end + [:bypass_filtering, :no_fancy_upsampling, :use_threads].each do |key| + options_struct[key] = 1 if @user_options[key] && true == @user_options[key] + end + [:crop_x, :crop_y, :crop_w, + :crop_h, :resize_w, :resize_h].each do |key| + options_struct[key] = @user_options[key] if @user_options[key] + end options_pointer end @@ -38,13 +45,19 @@ def decode_pointer def encode_default(options_struct) options_struct[:quality] = 100 - options_struct[:crop_x] = options_struct[:crop_y] = 0 - options_struct[:crop_w] = options_struct[:crop_h] = 0 - options_struct[:resize_w] = options_struct[:resize_h] = 0 + similar_default(options_struct) end def decode_default(options_struct) + # default format is png options_struct[:output_format] = C::OutputFileFormat[:png] + similar_default(options_struct) + end + + def similar_default(options_struct) + options_struct[:crop_x] = options_struct[:crop_y] = 0 + options_struct[:crop_w] = options_struct[:crop_h] = 0 + options_struct[:resize_w] = options_struct[:resize_h] = 0 end end diff --git a/spec/factories/5.jpg b/spec/factories/3.jpg similarity index 100% rename from spec/factories/5.jpg rename to spec/factories/3.jpg diff --git a/spec/factories/3.png b/spec/factories/3.png deleted file mode 100644 index d8a592f..0000000 Binary files a/spec/factories/3.png and /dev/null differ diff --git a/spec/factories/3.webp b/spec/factories/3.webp index fc4e2ec..122741b 100644 Binary files a/spec/factories/3.webp and b/spec/factories/3.webp differ diff --git a/spec/factories/6.jpg b/spec/factories/4.jpg similarity index 100% rename from spec/factories/6.jpg rename to spec/factories/4.jpg diff --git a/spec/factories/4.png b/spec/factories/4.png deleted file mode 100644 index 1704ace..0000000 Binary files a/spec/factories/4.png and /dev/null differ diff --git a/spec/factories/4.webp b/spec/factories/4.webp index 8078123..a608fc8 100644 Binary files a/spec/factories/4.webp and b/spec/factories/4.webp differ diff --git a/spec/factories/7.tif b/spec/factories/5.tif similarity index 100% rename from spec/factories/7.tif rename to spec/factories/5.tif diff --git a/spec/factories/5.webp b/spec/factories/5.webp index 122741b..0f6dca1 100644 Binary files a/spec/factories/5.webp and b/spec/factories/5.webp differ diff --git a/spec/factories/6.webp b/spec/factories/6.webp deleted file mode 100644 index a608fc8..0000000 Binary files a/spec/factories/6.webp and /dev/null differ diff --git a/spec/factories/7.webp b/spec/factories/7.webp deleted file mode 100644 index 0f6dca1..0000000 Binary files a/spec/factories/7.webp and /dev/null differ diff --git a/spec/webp_ffi_spec.rb b/spec/webp_ffi_spec.rb index c572a4a..d957a26 100644 --- a/spec/webp_ffi_spec.rb +++ b/spec/webp_ffi_spec.rb @@ -2,10 +2,10 @@ describe WebP do factories = { - webp: ["1", "2", "3", "4", "5", "6", "7"], - png: ["1", "2", "3", "4"], - jpg: ["5", "6"], - tiff: ["7"], + webp: ["1", "2", "3", "4", "5"], + png: ["1", "2"], + jpg: ["3", "4"], + tiff: ["5"], info: { "1" => { size: [400, 301], @@ -16,22 +16,14 @@ has_alpha: true }, "3" => { - size: [300, 300], - has_alpha: true - }, - "4" => { - size: [2000, 2353], - has_alpha: true - }, - "5" => { size: [550, 368], has_alpha: false }, - "6" => { + "4" => { size: [1024, 772], has_alpha: false }, - "7" => { + "5" => { size: [1419, 1001], has_alpha: false } @@ -42,6 +34,12 @@ @out_dir = File.expand_path(File.join(File.dirname(__FILE__), "../tmp/")) Dir.mkdir(@out_dir) unless File.exists?(@out_dir) end + after :all do + @out_dir = File.expand_path(File.join(File.dirname(__FILE__), "../tmp/")) + #Dir["#{@out_dir}/*{.png,.webp}"].each do |file| + # File.delete(file) rescue nil + #end + end it "calculate plus 100 by test_c (verify C)" do WebP::C.test_c(100).should == 200 @@ -105,24 +103,22 @@ expect { WebP.encode(in_filename, out_filename) }.to raise_error WebP::EncoderError end end - end - - context "encode with options" do - factories[:png].each do |image| - it "#{image}.png image" do - in_filename = File.expand_path(File.join(File.dirname(__FILE__), "factories/#{image}.png")) - out_filename = File.expand_path(File.join(@out_dir, "#{image}.50png.webp")) - WebP.encode(in_filename, out_filename, quality: 50, method: 0, alpha_quality: 10, alpha_compression: 1) + context "with options" do + factories[:png].each do |image| + it "#{image}.png image" do + in_filename = File.expand_path(File.join(File.dirname(__FILE__), "factories/#{image}.png")) + out_filename = File.expand_path(File.join(@out_dir, "#{image}.50png.webp")) + WebP.encode(in_filename, out_filename, quality: 50, method: 0, alpha_quality: 10, alpha_compression: 1) + end end end - end - - context "raise EncoderError on invalid crop options" do - factories[:png].each do |image| - it "#{image}.png image" do - in_filename = File.expand_path(File.join(File.dirname(__FILE__), "factories/#{image}.png")) - out_filename = File.expand_path(File.join(@out_dir, "#{image}.invpng.webp")) - expect { WebP.encode(in_filename, out_filename, crop_w: 30000) }.to raise_error WebP::EncoderError + context "raise EncoderError on invalid crop options" do + factories[:png].each do |image| + it "#{image}.png image" do + in_filename = File.expand_path(File.join(File.dirname(__FILE__), "factories/#{image}.png")) + out_filename = File.expand_path(File.join(@out_dir, "#{image}.invpng.webp")) + expect { WebP.encode(in_filename, out_filename, crop_w: 30000) }.to raise_error WebP::EncoderError + end end end end @@ -135,15 +131,46 @@ WebP.decode(in_filename, out_filename).should be_true end end - end - - context "decode with output_format" do - [:pam, :ppm].each do |output_format| - factories[:webp].each do |image| - it "#{image}.webp image to #{output_format}" do + context "with output_format" do + [:png, :pam, :ppm, :pgm, :alpha_plane_only].each do |output_format| + factories[:webp].take(2).each do |image| + it "#{image}.webp image to #{output_format}" do + in_filename = File.expand_path(File.join(File.dirname(__FILE__), "factories/#{image}.webp")) + out_filename = File.expand_path(File.join(@out_dir, "#{image}.#{output_format}.png")) + WebP.decode(in_filename, out_filename, output_format: output_format).should be_true + end + end + end + end + context "with options" do + factories[:webp].take(2).each do |image| + it "#{image}.webp image to png and crop" do in_filename = File.expand_path(File.join(File.dirname(__FILE__), "factories/#{image}.webp")) - out_filename = File.expand_path(File.join(@out_dir, "#{image}.#{output_format}.png")) - WebP.decode(in_filename, out_filename, output_format: output_format).should be_true + out_filename = File.expand_path(File.join(@out_dir, "#{image}_crop.png")) + WebP.decode(in_filename, out_filename, crop_w: 200, crop_h: 200).should be_true + end + it "#{image}.webp image to png and scale" do + in_filename = File.expand_path(File.join(File.dirname(__FILE__), "factories/#{image}.webp")) + out_filename = File.expand_path(File.join(@out_dir, "#{image}_resize.png")) + WebP.decode(in_filename, out_filename, resize_w: 200, resize_h: 200).should be_true + end + end + end + context "raise DecoderError on invalid webp image" do + factories[:png].each do |image| + it "#{image}.png image" do + in_filename = File.expand_path(File.join(File.dirname(__FILE__), "factories/#{image}.png")) + out_filename = File.expand_path(File.join(@out_dir, "#{image}.invpng.webp")) + expect { WebP.decode(in_filename, out_filename) }.to raise_error WebP::DecoderError + end + end + end + context "raise DecoderError on invalid options" do + factories[:png].each do |image| + it "#{image}.png image" do + in_filename = File.expand_path(File.join(File.dirname(__FILE__), "factories/#{image}.png")) + out_filename = File.expand_path(File.join(@out_dir, "#{image}.invpng.webp")) + expect { WebP.decode(in_filename, out_filename, crop_w: 30000) }.to raise_error WebP::DecoderError end end end