diff --git a/.ruby-version b/.ruby-version index 227cea2..7ec1d6d 100644 --- a/.ruby-version +++ b/.ruby-version @@ -1 +1 @@ -2.0.0 +2.1.0 diff --git a/ext/webp_ffi/util.c b/ext/webp_ffi/util.c index c42457e..a040740 100644 --- a/ext/webp_ffi/util.c +++ b/ext/webp_ffi/util.c @@ -280,6 +280,146 @@ static int UtilWritePPM(FILE* fout, const WebPDecBuffer* const buffer, int alpha return 1; } +static void PutLE16(uint8_t* const dst, uint32_t value) { + dst[0] = (value >> 0) & 0xff; + dst[1] = (value >> 8) & 0xff; +} + +static void PutLE32(uint8_t* const dst, uint32_t value) { + PutLE16(dst + 0, (value >> 0) & 0xffff); + PutLE16(dst + 2, (value >> 16) & 0xffff); +} + +#define BMP_HEADER_SIZE 54 +static int UtilWriteBMP(FILE* fout, const WebPDecBuffer* const buffer) { + const int has_alpha = (buffer->colorspace != MODE_BGR); + const uint32_t width = buffer->width; + const uint32_t height = buffer->height; + const uint8_t* const rgba = buffer->u.RGBA.rgba; + const int stride = buffer->u.RGBA.stride; + const uint32_t bytes_per_px = has_alpha ? 4 : 3; + uint32_t y; + const uint32_t line_size = bytes_per_px * width; + const uint32_t bmp_stride = (line_size + 3) & ~3; // pad to 4 + const uint32_t total_size = bmp_stride * height + BMP_HEADER_SIZE; + uint8_t bmp_header[BMP_HEADER_SIZE] = { 0 }; + + // bitmap file header + PutLE16(bmp_header + 0, 0x4d42); // signature 'BM' + PutLE32(bmp_header + 2, total_size); // size including header + PutLE32(bmp_header + 6, 0); // reserved + PutLE32(bmp_header + 10, BMP_HEADER_SIZE); // offset to pixel array + // bitmap info header + PutLE32(bmp_header + 14, 40); // DIB header size + PutLE32(bmp_header + 18, width); // dimensions + PutLE32(bmp_header + 22, -(int)height); // vertical flip! + PutLE16(bmp_header + 26, 1); // number of planes + PutLE16(bmp_header + 28, bytes_per_px * 8); // bits per pixel + PutLE32(bmp_header + 30, 0); // no compression (BI_RGB) + PutLE32(bmp_header + 34, 0); // image size (dummy) + PutLE32(bmp_header + 38, 2400); // x pixels/meter + PutLE32(bmp_header + 42, 2400); // y pixels/meter + PutLE32(bmp_header + 46, 0); // number of palette colors + PutLE32(bmp_header + 50, 0); // important color count + + // TODO(skal): color profile + + // write header + if (fwrite(bmp_header, sizeof(bmp_header), 1, fout) != 1) { + return 0; + } + + // write pixel array + for (y = 0; y < height; ++y) { + if (fwrite(rgba + y * stride, line_size, 1, fout) != 1) { + return 0; + } + // write padding zeroes + if (bmp_stride != line_size) { + const uint8_t zeroes[3] = { 0 }; + if (fwrite(zeroes, bmp_stride - line_size, 1, fout) != 1) { + return 0; + } + } + } + return 1; +} +#undef BMP_HEADER_SIZE + +#define NUM_IFD_ENTRIES 15 +#define EXTRA_DATA_SIZE 16 +// 10b for signature/header + n * 12b entries + 4b for IFD terminator: +#define EXTRA_DATA_OFFSET (10 + 12 * NUM_IFD_ENTRIES + 4) +#define TIFF_HEADER_SIZE (EXTRA_DATA_OFFSET + EXTRA_DATA_SIZE) + +static int UtilWriteTIFF(FILE* fout, const WebPDecBuffer* const buffer) { + const int has_alpha = (buffer->colorspace != MODE_RGB); + const uint32_t width = buffer->width; + const uint32_t height = buffer->height; + const uint8_t* const rgba = buffer->u.RGBA.rgba; + const int stride = buffer->u.RGBA.stride; + const uint8_t bytes_per_px = has_alpha ? 4 : 3; + // For non-alpha case, we omit tag 0x152 (ExtraSamples). + const uint8_t num_ifd_entries = has_alpha ? NUM_IFD_ENTRIES + : NUM_IFD_ENTRIES - 1; + uint8_t tiff_header[TIFF_HEADER_SIZE] = { + 0x49, 0x49, 0x2a, 0x00, // little endian signature + 8, 0, 0, 0, // offset to the unique IFD that follows + // IFD (offset = 8). Entries must be written in increasing tag order. + num_ifd_entries, 0, // Number of entries in the IFD (12 bytes each). + 0x00, 0x01, 3, 0, 1, 0, 0, 0, 0, 0, 0, 0, // 10: Width (TBD) + 0x01, 0x01, 3, 0, 1, 0, 0, 0, 0, 0, 0, 0, // 22: Height (TBD) + 0x02, 0x01, 3, 0, bytes_per_px, 0, 0, 0, // 34: BitsPerSample: 8888 + EXTRA_DATA_OFFSET + 0, 0, 0, 0, + 0x03, 0x01, 3, 0, 1, 0, 0, 0, 1, 0, 0, 0, // 46: Compression: none + 0x06, 0x01, 3, 0, 1, 0, 0, 0, 2, 0, 0, 0, // 58: Photometric: RGB + 0x11, 0x01, 4, 0, 1, 0, 0, 0, // 70: Strips offset: + TIFF_HEADER_SIZE, 0, 0, 0, // data follows header + 0x12, 0x01, 3, 0, 1, 0, 0, 0, 1, 0, 0, 0, // 82: Orientation: topleft + 0x15, 0x01, 3, 0, 1, 0, 0, 0, // 94: SamplesPerPixels + bytes_per_px, 0, 0, 0, + 0x16, 0x01, 3, 0, 1, 0, 0, 0, 0, 0, 0, 0, // 106: Rows per strip (TBD) + 0x17, 0x01, 4, 0, 1, 0, 0, 0, 0, 0, 0, 0, // 118: StripByteCount (TBD) + 0x1a, 0x01, 5, 0, 1, 0, 0, 0, // 130: X-resolution + EXTRA_DATA_OFFSET + 8, 0, 0, 0, + 0x1b, 0x01, 5, 0, 1, 0, 0, 0, // 142: Y-resolution + EXTRA_DATA_OFFSET + 8, 0, 0, 0, + 0x1c, 0x01, 3, 0, 1, 0, 0, 0, 1, 0, 0, 0, // 154: PlanarConfiguration + 0x28, 0x01, 3, 0, 1, 0, 0, 0, 2, 0, 0, 0, // 166: ResolutionUnit (inch) + 0x52, 0x01, 3, 0, 1, 0, 0, 0, 1, 0, 0, 0, // 178: ExtraSamples: rgbA + 0, 0, 0, 0, // 190: IFD terminator + // EXTRA_DATA_OFFSET: + 8, 0, 8, 0, 8, 0, 8, 0, // BitsPerSample + 72, 0, 0, 0, 1, 0, 0, 0 // 72 pixels/inch, for X/Y-resolution + }; + uint32_t y; + + // Fill placeholders in IFD: + PutLE32(tiff_header + 10 + 8, width); + PutLE32(tiff_header + 22 + 8, height); + PutLE32(tiff_header + 106 + 8, height); + PutLE32(tiff_header + 118 + 8, width * bytes_per_px * height); + if (!has_alpha) PutLE32(tiff_header + 178, 0); // IFD terminator + + // write header + if (fwrite(tiff_header, sizeof(tiff_header), 1, fout) != 1) { + return 0; + } + // write pixel values + for (y = 0; y < height; ++y) { + if (fwrite(rgba + y * stride, bytes_per_px, width, fout) != width) { + return 0; + } + } + + return 1; +} + +#undef TIFF_HEADER_SIZE +#undef EXTRA_DATA_OFFSET +#undef EXTRA_DATA_SIZE +#undef NUM_IFD_ENTRIES + static int UtilWriteAlphaPlane(FILE* fout, const WebPDecBuffer* const buffer) { const uint32_t width = buffer->width; const uint32_t height = buffer->height; @@ -389,11 +529,11 @@ static InputFileFormat GetImageType(FILE* in_file) { magic = (buf[0] << 24) | (buf[1] << 16) | (buf[2] << 8) | buf[3]; if (magic == 0x89504E47U) { - format = PNG_; + format = iPNG_; } else if (magic >= 0xFFD8FF00U && magic <= 0xFFD8FFFFU) { - format = JPEG_; + format = iJPEG_; } else if (magic == 0x49492A00 || magic == 0x4D4D002A) { - format = TIFF_; + format = iTIFF_; } return format; } @@ -410,11 +550,11 @@ int UtilReadPicture(const char* const filename, WebPPicture* const pic, if (pic->width == 0 || pic->height == 0) { // If no size specified, try to decode it as PNG/JPEG (as appropriate). const InputFileFormat format = GetImageType(in_file); - if (format == PNG_) { + if (format == iPNG_) { ok = UtilReadPNG(in_file, pic, keep_alpha); - } else if (format == JPEG_) { + } else if (format == iJPEG_) { ok = UtilReadJPEG(in_file, pic); - } else if (format == TIFF_) { + } else if (format == iTIFF_) { ok = UtilReadTIFF(filename, pic, keep_alpha); } } else { @@ -437,13 +577,17 @@ int UtilSaveOutput(const WebPDecBuffer* const buffer, return 0; } - if (format == PNG) { + if (format == oPNG) { ok &= UtilWritePNG(fout, buffer); - } else if (format == PAM) { + } else if (format == oPAM) { ok &= UtilWritePPM(fout, buffer, 1); - } else if (format == PPM) { + } else if (format == oPPM) { ok &= UtilWritePPM(fout, buffer, 0); - } else if (format == PGM) { + } else if (format == oBMP) { + ok &= UtilWriteBMP(fout, buffer); + } else if (format == oTIFF_) { + ok &= UtilWriteTIFF(fout, buffer); + } else if (format == oPGM || format == oYUV) { ok &= UtilWritePGM(fout, buffer); } else if (format == ALPHA_PLANE_ONLY) { ok &= UtilWriteAlphaPlane(fout, buffer); diff --git a/ext/webp_ffi/util.h b/ext/webp_ffi/util.h index 874ccf8..1523645 100644 --- a/ext/webp_ffi/util.h +++ b/ext/webp_ffi/util.h @@ -6,17 +6,20 @@ extern "C" { #endif typedef enum { - PNG = 0, - PAM, - PPM, - PGM, + oPNG = 0, + oPAM, + oPPM, + oPGM, + oBMP, + oTIFF_, + oYUV, ALPHA_PLANE_ONLY // this is for experimenting only } OutputFileFormat; typedef enum { - PNG_ = 0, - JPEG_, - TIFF_, // 'TIFF' clashes with libtiff + iPNG_ = 0, + iJPEG_, + iTIFF_, // 'TIFF' clashes with libtiff UNSUPPORTED } InputFileFormat; diff --git a/ext/webp_ffi/webp_ffi.c b/ext/webp_ffi/webp_ffi.c index 82eadd6..5f7dbbf 100644 --- a/ext/webp_ffi/webp_ffi.c +++ b/ext/webp_ffi/webp_ffi.c @@ -3,14 +3,10 @@ #include #include -#include "webp/decode.h" -#include "webp/encode.h" - // utils #include "./util.h" #include "./webp_ffi.h" - #if defined(__cplusplus) || defined(c_plusplus) extern "C" { #endif @@ -193,7 +189,7 @@ int webp_decode(const char *in_file, const char *out_file, const FfiWebpDecodeCo WebPDecoderConfig config; WebPDecBuffer* const output_buffer = &config.output; WebPBitstreamFeatures* const bitstream = &config.input; - OutputFileFormat format = PNG; + OutputFileFormat format = oPNG; if (!WebPInitDecoderConfig(&config)) { //fprintf(stderr, "Library version mismatch!\n"); @@ -239,16 +235,24 @@ int webp_decode(const char *in_file, const char *out_file, const FfiWebpDecodeCo } switch (format) { - case PNG: + case oPNG: output_buffer->colorspace = bitstream->has_alpha ? MODE_RGBA : MODE_RGB; break; - case PAM: + case oPAM: output_buffer->colorspace = MODE_RGBA; break; - case PPM: + case oPPM: output_buffer->colorspace = MODE_RGB; // drops alpha for PPM break; - case PGM: + case oBMP: + output_buffer->colorspace = bitstream->has_alpha ? MODE_BGRA : MODE_BGR; + break; + case oTIFF_: // note: force pre-multiplied alpha + output_buffer->colorspace = + bitstream->has_alpha ? MODE_rgbA : MODE_RGB; + break; + case oPGM: + case oYUV: output_buffer->colorspace = bitstream->has_alpha ? MODE_YUVA : MODE_YUV; break; case ALPHA_PLANE_ONLY: @@ -258,6 +262,7 @@ int webp_decode(const char *in_file, const char *out_file, const FfiWebpDecodeCo free((void*)data); return 3; } + status = WebPDecode(data, data_size, &config); if (status != VP8_STATUS_OK) { diff --git a/lib/webp/c.rb b/lib/webp/c.rb index 423b1ec..04f0499 100644 --- a/lib/webp/c.rb +++ b/lib/webp/c.rb @@ -5,6 +5,9 @@ module C :pam, :ppm, :pgm, + :bmp, + :tiff, + :yuv, :alpha_plane_only ) # struct class FfiWebpEncodeConfig < FFI::Struct @@ -37,7 +40,7 @@ class FfiWebpEncodeConfig < FFI::Struct :resize_w, :int, :resize_h, :int end - + class FfiWebpDecodeConfig < FFI::Struct layout :output_format, OutputFileFormat, :bypass_filtering, :int, diff --git a/spec/travis_build.sh b/spec/travis_build.sh index 6c4de4f..f762b97 100755 --- a/spec/travis_build.sh +++ b/spec/travis_build.sh @@ -1,7 +1,7 @@ #/usr/bin/env sh -wget http://webp.googlecode.com/files/libwebp-0.3.1.tar.gz -tar xvzf libwebp-0.3.1.tar.gz -cd libwebp-0.3.1 +wget http://webp.googlecode.com/files/libwebp-0.4.0.tar.gz +tar xvzf libwebp-0.4.0.tar.gz +cd libwebp-0.4.0 ./configure make sudo make install diff --git a/spec/webp_ffi_spec.rb b/spec/webp_ffi_spec.rb index cf0f6d4..9295304 100644 --- a/spec/webp_ffi_spec.rb +++ b/spec/webp_ffi_spec.rb @@ -132,7 +132,7 @@ end end context "with output_format" do - [:png, :pam, :ppm, :pgm, :alpha_plane_only].each do |output_format| + [:png, :pam, :ppm, :pgm, :bmp, :tiff, :yuv, :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"))