From af1ad3e2dda3ba144c55d4959c73e0b53e7a329b Mon Sep 17 00:00:00 2001 From: Pascal Massimino Date: Tue, 6 Sep 2016 22:46:31 -0700 Subject: [PATCH] libimageenc.a: extract image-saving code from dwebp BUG=webp:277 Change-Id: I2c0e1df7b13b1f77474b5478048fef022e90f77a --- CMakeLists.txt | 14 +- Makefile.vc | 13 +- build.gradle | 19 ++ examples/Android.mk | 2 +- examples/Makefile.am | 7 +- examples/dwebp.c | 500 +------------------------------------- imageio/Android.mk | 16 ++ imageio/Makefile.am | 6 +- imageio/image_enc.c | 558 +++++++++++++++++++++++++++++++++++++++++++ imageio/image_enc.h | 96 ++++++++ makefile.unix | 12 +- 11 files changed, 736 insertions(+), 507 deletions(-) create mode 100644 imageio/image_enc.c create mode 100644 imageio/image_enc.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 25b085eb..e7fd02e1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -128,6 +128,14 @@ if(WEBP_BUILD_CWEBP OR WEBP_BUILD_DWEBP) add_library(imagedec ${imagedec_SRCS}) target_link_libraries(imagedec webp ${WEBP_DEP_LIBRARIES} ${WEBP_DEP_IMG_LIBRARIES}) + + # Image-encoding utility library. + set(imageenc_SRCS + ${CMAKE_CURRENT_SOURCE_DIR}/imageio/image_enc.c + ${CMAKE_CURRENT_SOURCE_DIR}/imageio/image_enc.h) + add_library(imageenc ${imageenc_SRCS}) + target_link_libraries(imageenc webp imageioutil + ${WEBP_DEP_LIBRARIES} ${WEBP_DEP_IMG_LIBRARIES}) endif() if(WEBP_BUILD_DWEBP) @@ -135,9 +143,9 @@ if(WEBP_BUILD_DWEBP) include_directories(${WEBP_DEP_IMG_INCLUDE_DIRS}) add_executable(dwebp ${CMAKE_CURRENT_SOURCE_DIR}/examples/dwebp.c - ${CMAKE_CURRENT_SOURCE_DIR}/examples/stopwatch.h - ) - target_link_libraries(dwebp imagedec webp exampleutil imageioutil + ${CMAKE_CURRENT_SOURCE_DIR}/examples/stopwatch.h) + target_link_libraries(dwebp imagedec imageenc webp + exampleutil imageioutil ${WEBP_DEP_LIBRARIES} ${WEBP_DEP_IMG_LIBRARIES} ) endif() diff --git a/Makefile.vc b/Makefile.vc index 07cad9f5..24d373fe 100644 --- a/Makefile.vc +++ b/Makefile.vc @@ -251,7 +251,7 @@ DSP_ENC_OBJS = \ EX_ANIM_UTIL_OBJS = \ $(DIROBJ)\examples\anim_util.obj \ -EX_FORMAT_DEC_OBJS = \ +IMAGEIO_DEC_OBJS = \ $(DIROBJ)\imageio\image_dec.obj \ $(DIROBJ)\imageio\jpegdec.obj \ $(DIROBJ)\imageio\metadata.obj \ @@ -260,6 +260,9 @@ EX_FORMAT_DEC_OBJS = \ $(DIROBJ)\imageio\webpdec.obj \ $(DIROBJ)\imageio\wicdec.obj \ +IMAGEIO_ENC_OBJS = \ + $(DIROBJ)\imageio\image_enc.obj \ + EX_GIF_DEC_OBJS = \ $(DIROBJ)\examples\gifdec.obj \ @@ -339,9 +342,10 @@ anim_diff: $(DIRBIN)\anim_diff.exe $(DIRBIN)\anim_diff.exe: $(DIROBJ)\examples\anim_diff.obj $(EX_ANIM_UTIL_OBJS) $(DIRBIN)\anim_diff.exe: $(EX_UTIL_OBJS) $(IMAGEIO_UTIL_OBJS) $(DIRBIN)\anim_diff.exe: $(EX_GIF_DEC_OBJS) $(LIBWEBPDEMUX) $(LIBWEBP) -$(DIRBIN)\cwebp.exe: $(DIROBJ)\examples\cwebp.obj $(EX_FORMAT_DEC_OBJS) +$(DIRBIN)\cwebp.exe: $(DIROBJ)\examples\cwebp.obj $(IMAGEIO_DEC_OBJS) $(DIRBIN)\cwebp.exe: $(IMAGEIO_UTIL_OBJS) -$(DIRBIN)\dwebp.exe: $(DIROBJ)\examples\dwebp.obj $(EX_FORMAT_DEC_OBJS) +$(DIRBIN)\dwebp.exe: $(DIROBJ)\examples\dwebp.obj $(IMAGEIO_DEC_OBJS) +$(DIRBIN)\dwebp.exe: $(IMAGEIO_ENC_OBJS) $(DIRBIN)\dwebp.exe: $(IMAGEIO_UTIL_OBJS) $(DIRBIN)\gif2webp.exe: $(DIROBJ)\examples\gif2webp.obj $(EX_GIF_DEC_OBJS) $(DIRBIN)\gif2webp.exe: $(EX_UTIL_OBJS) $(IMAGEIO_UTIL_OBJS) $(LIBWEBPMUX) @@ -351,7 +355,8 @@ $(DIRBIN)\vwebp.exe: $(IMAGEIO_UTIL_OBJS) $(LIBWEBPDEMUX) $(LIBWEBP) $(DIRBIN)\webpmux.exe: $(DIROBJ)\examples\webpmux.obj $(LIBWEBPMUX) $(DIRBIN)\webpmux.exe: $(EX_UTIL_OBJS) $(IMAGEIO_UTIL_OBJS) $(LIBWEBP) $(OUT_EXAMPLES): $(EX_UTIL_OBJS) $(LIBWEBP) -$(EX_UTIL_OBJS) $(EX_FORMAT_DEC_OBJS) $(IMAGEIO_UTIL_OBJS): $(OUTPUT_DIRS) +$(EX_UTIL_OBJS) $(IMAGEIO_UTIL_OBJS): $(OUTPUT_DIRS) +$(IMAGEIO_DEC_OBJS) $(IMAGEIO_ENC_OBJS): $(OUTPUT_DIRS) !ENDIF # ARCH == ARM experimental: diff --git a/build.gradle b/build.gradle index 9438ffdc..b17caf9c 100644 --- a/build.gradle +++ b/build.gradle @@ -292,6 +292,24 @@ model { } } } + + imageenc(NativeLibrarySpec) { + binaries { + all { + lib library: "webp", linkage: "static" + lib library: "imageio_util", linkage: "static" + } + } + sources { + c { + source { + srcDir "./imageio" + include "image_enc.c" + } + } + } + } + cwebp(NativeExecutableSpec) { binaries { all { @@ -316,6 +334,7 @@ model { all { lib library: "example_util", linkage: "static" lib library: "imagedec", linkage: "static" + lib library: "imageenc", linkage: "static" lib library: "imageio_util", linkage: "static" lib library: "webp" } diff --git a/examples/Android.mk b/examples/Android.mk index b79113e1..3323ede1 100644 --- a/examples/Android.mk +++ b/examples/Android.mk @@ -43,7 +43,7 @@ LOCAL_SRC_FILES := \ LOCAL_CFLAGS := $(WEBP_CFLAGS) LOCAL_C_INCLUDES := $(LOCAL_PATH)/../src -LOCAL_STATIC_LIBRARIES := example_util imageio_util imagedec webp +LOCAL_STATIC_LIBRARIES := example_util imageio_util imagedec imageenc webp LOCAL_MODULE := dwebp diff --git a/examples/Makefile.am b/examples/Makefile.am index 7618d7fc..8adb1d6a 100644 --- a/examples/Makefile.am +++ b/examples/Makefile.am @@ -34,8 +34,11 @@ cwebp_LDADD += $(JPEG_LIBS) $(PNG_LIBS) $(TIFF_LIBS) dwebp_SOURCES = dwebp.c stopwatch.h dwebp_CPPFLAGS = $(AM_CPPFLAGS) $(USE_EXPERIMENTAL_CODE) dwebp_CPPFLAGS += $(JPEG_INCLUDES) $(PNG_INCLUDES) -dwebp_LDADD = libexample_util.la ../imageio/libimageio_util.la -dwebp_LDADD += ../imageio/libimagedec.la ../src/libwebp.la +dwebp_LDADD = libexample_util.la +dwebp_LDADD += ../imageio/libimagedec.la +dwebp_LDADD += ../imageio/libimageenc.la +dwebp_LDADD += ../imageio/libimageio_util.la +dwebp_LDADD += ../src/libwebp.la dwebp_LDADD +=$(PNG_LIBS) $(JPEG_LIBS) gif2webp_SOURCES = gif2webp.c gifdec.c gifdec.h diff --git a/examples/dwebp.c b/examples/dwebp.c index 6f0af8d6..19bdcced 100644 --- a/examples/dwebp.c +++ b/examples/dwebp.c @@ -20,28 +20,8 @@ #include "webp/config.h" #endif -#ifdef WEBP_HAVE_PNG -#include -#include // note: this must be included *after* png.h -#endif - -#ifdef HAVE_WINCODEC_H -#ifdef __MINGW32__ -#define INITGUID // Without this GUIDs are declared extern and fail to link -#endif -#define CINTERFACE -#define COBJMACROS -#define _WIN32_IE 0x500 // Workaround bug in shlwapi.h when compiling C++ - // code with COBJMACROS. -#include // CreateStreamOnHGlobal() -#include -#include -#include -#endif - -#include "webp/decode.h" #include "../examples/example_util.h" -#include "../imageio/imageio_util.h" +#include "../imageio/image_enc.h" #include "../imageio/webpdec.h" #include "./stopwatch.h" @@ -59,486 +39,18 @@ extern void* VP8GetCPUInfo; // opaque forward declaration. #endif #endif // WEBP_DLL -//------------------------------------------------------------------------------ - -// Output types -typedef enum { - PNG = 0, - PAM, - PPM, - PGM, - BMP, - TIFF, - RAW_YUV, - ALPHA_PLANE_ONLY, // this is for experimenting only - // forced colorspace output (for testing, mostly) - RGB, RGBA, BGR, BGRA, ARGB, - RGBA_4444, RGB_565, - rgbA, bgrA, Argb, rgbA_4444, - YUV, YUVA -} OutputFileFormat; - -#ifdef HAVE_WINCODEC_H - -#define IFS(fn) \ - do { \ - if (SUCCEEDED(hr)) { \ - hr = (fn); \ - if (FAILED(hr)) fprintf(stderr, #fn " failed %08lx\n", hr); \ - } \ - } while (0) - -#ifdef __cplusplus -#define MAKE_REFGUID(x) (x) -#else -#define MAKE_REFGUID(x) &(x) -#endif - -static HRESULT CreateOutputStream(const char* out_file_name, - int write_to_mem, IStream** stream) { - HRESULT hr = S_OK; - if (write_to_mem) { - // Output to a memory buffer. This is freed when 'stream' is released. - IFS(CreateStreamOnHGlobal(NULL, TRUE, stream)); - } else { - IFS(SHCreateStreamOnFileA(out_file_name, STGM_WRITE | STGM_CREATE, stream)); - } - if (FAILED(hr)) { - fprintf(stderr, "Error opening output file %s (%08lx)\n", - out_file_name, hr); - } - return hr; -} - -static HRESULT WriteUsingWIC(const char* out_file_name, int use_stdout, - REFGUID container_guid, - uint8_t* rgb, int stride, - uint32_t width, uint32_t height, int has_alpha) { - HRESULT hr = S_OK; - IWICImagingFactory* factory = NULL; - IWICBitmapFrameEncode* frame = NULL; - IWICBitmapEncoder* encoder = NULL; - IStream* stream = NULL; - WICPixelFormatGUID pixel_format = has_alpha ? GUID_WICPixelFormat32bppBGRA - : GUID_WICPixelFormat24bppBGR; - - IFS(CoInitialize(NULL)); - IFS(CoCreateInstance(MAKE_REFGUID(CLSID_WICImagingFactory), NULL, - CLSCTX_INPROC_SERVER, - MAKE_REFGUID(IID_IWICImagingFactory), - (LPVOID*)&factory)); - if (hr == REGDB_E_CLASSNOTREG) { - fprintf(stderr, - "Couldn't access Windows Imaging Component (are you running " - "Windows XP SP3 or newer?). PNG support not available. " - "Use -ppm or -pgm for available PPM and PGM formats.\n"); - } - IFS(CreateOutputStream(out_file_name, use_stdout, &stream)); - IFS(IWICImagingFactory_CreateEncoder(factory, container_guid, NULL, - &encoder)); - IFS(IWICBitmapEncoder_Initialize(encoder, stream, - WICBitmapEncoderNoCache)); - IFS(IWICBitmapEncoder_CreateNewFrame(encoder, &frame, NULL)); - IFS(IWICBitmapFrameEncode_Initialize(frame, NULL)); - IFS(IWICBitmapFrameEncode_SetSize(frame, width, height)); - IFS(IWICBitmapFrameEncode_SetPixelFormat(frame, &pixel_format)); - IFS(IWICBitmapFrameEncode_WritePixels(frame, height, stride, - height * stride, rgb)); - IFS(IWICBitmapFrameEncode_Commit(frame)); - IFS(IWICBitmapEncoder_Commit(encoder)); - - if (SUCCEEDED(hr) && use_stdout) { - HGLOBAL image; - IFS(GetHGlobalFromStream(stream, &image)); - if (SUCCEEDED(hr)) { - HANDLE std_output = GetStdHandle(STD_OUTPUT_HANDLE); - DWORD mode; - const BOOL update_mode = GetConsoleMode(std_output, &mode); - const void* const image_mem = GlobalLock(image); - DWORD bytes_written = 0; - - // Clear output processing if necessary, then output the image. - if (update_mode) SetConsoleMode(std_output, 0); - if (!WriteFile(std_output, image_mem, (DWORD)GlobalSize(image), - &bytes_written, NULL) || - bytes_written != GlobalSize(image)) { - hr = E_FAIL; - } - if (update_mode) SetConsoleMode(std_output, mode); - GlobalUnlock(image); - } - } - - if (frame != NULL) IUnknown_Release(frame); - if (encoder != NULL) IUnknown_Release(encoder); - if (factory != NULL) IUnknown_Release(factory); - if (stream != NULL) IUnknown_Release(stream); - return hr; -} - -static int WritePNG(const char* out_file_name, int use_stdout, - const WebPDecBuffer* const buffer) { - const uint32_t width = buffer->width; - const uint32_t height = buffer->height; - uint8_t* const rgb = buffer->u.RGBA.rgba; - const int stride = buffer->u.RGBA.stride; - const int has_alpha = WebPIsAlphaMode(buffer->colorspace); - - return SUCCEEDED(WriteUsingWIC(out_file_name, use_stdout, - MAKE_REFGUID(GUID_ContainerFormatPng), - rgb, stride, width, height, has_alpha)); -} - -#elif defined(WEBP_HAVE_PNG) // !HAVE_WINCODEC_H -static void PNGAPI PNGErrorFunction(png_structp png, png_const_charp dummy) { - (void)dummy; // remove variable-unused warning - longjmp(png_jmpbuf(png), 1); -} - -static int WritePNG(FILE* out_file, const WebPDecBuffer* const buffer) { - const uint32_t width = buffer->width; - const uint32_t height = buffer->height; - uint8_t* const rgb = buffer->u.RGBA.rgba; - const int stride = buffer->u.RGBA.stride; - const int has_alpha = WebPIsAlphaMode(buffer->colorspace); - volatile png_structp png; - volatile png_infop info; - png_uint_32 y; - - png = png_create_write_struct(PNG_LIBPNG_VER_STRING, - NULL, PNGErrorFunction, NULL); - if (png == NULL) { - return 0; - } - info = png_create_info_struct(png); - if (info == NULL) { - png_destroy_write_struct((png_structpp)&png, NULL); - return 0; - } - if (setjmp(png_jmpbuf(png))) { - png_destroy_write_struct((png_structpp)&png, (png_infopp)&info); - return 0; - } - png_init_io(png, out_file); - png_set_IHDR(png, info, width, height, 8, - has_alpha ? PNG_COLOR_TYPE_RGBA : PNG_COLOR_TYPE_RGB, - PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT, - PNG_FILTER_TYPE_DEFAULT); - png_write_info(png, info); - for (y = 0; y < height; ++y) { - png_bytep row = rgb + y * stride; - png_write_rows(png, &row, 1); - } - png_write_end(png, info); - png_destroy_write_struct((png_structpp)&png, (png_infopp)&info); - return 1; -} -#else // !HAVE_WINCODEC_H && !WEBP_HAVE_PNG -static int WritePNG(FILE* out_file, const WebPDecBuffer* const buffer) { - (void)out_file; - (void)buffer; - fprintf(stderr, "PNG support not compiled. Please install the libpng " - "development package before building.\n"); - fprintf(stderr, "You can run with -ppm flag to decode in PPM format.\n"); - return 0; -} -#endif - -static int WritePPM(FILE* fout, const WebPDecBuffer* const buffer, int alpha) { - const uint32_t width = buffer->width; - const uint32_t height = buffer->height; - const uint8_t* const rgb = buffer->u.RGBA.rgba; - const int stride = buffer->u.RGBA.stride; - const size_t bytes_per_px = alpha ? 4 : 3; - uint32_t y; - - if (alpha) { - fprintf(fout, "P7\nWIDTH %u\nHEIGHT %u\nDEPTH 4\nMAXVAL 255\n" - "TUPLTYPE RGB_ALPHA\nENDHDR\n", width, height); - } else { - fprintf(fout, "P6\n%u %u\n255\n", width, height); - } - for (y = 0; y < height; ++y) { - if (fwrite(rgb + y * stride, width, bytes_per_px, fout) != bytes_per_px) { - return 0; - } - } - return 1; -} - -// Save 16b mode (RGBA4444, RGB565, ...) for debugging purpose. -static int Write16bAsPGM(FILE* fout, const WebPDecBuffer* const buffer) { - 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 = 2; - uint32_t y; - - fprintf(fout, "P5\n%u %u\n255\n", width * bytes_per_px, height); - for (y = 0; y < height; ++y) { - if (fwrite(rgba + y * stride, width, bytes_per_px, fout) != bytes_per_px) { - return 0; - } - } - 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 WriteBMP(FILE* fout, const WebPDecBuffer* const buffer) { - const int has_alpha = WebPIsAlphaMode(buffer->colorspace); - 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 WriteTIFF(FILE* fout, const WebPDecBuffer* const buffer) { - const int has_alpha = WebPIsAlphaMode(buffer->colorspace); - 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 WriteAlphaPlane(FILE* fout, const WebPDecBuffer* const buffer) { - const uint32_t width = buffer->width; - const uint32_t height = buffer->height; - const uint8_t* 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%u %u\n255\n", width, height); - for (y = 0; y < height; ++y) { - if (fwrite(a + y * a_stride, width, 1, fout) != 1) { - return 0; - } - } - return 1; -} - -// format=PGM: 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. -// format=RAW_YUV: just save the Y/U/V/A planes sequentially without header. -static int WritePGMOrYUV(FILE* fout, const WebPDecBuffer* const buffer, - OutputFileFormat format) { - const int width = buffer->width; - const int height = buffer->height; - const WebPYUVABuffer* const yuv = &buffer->u.YUVA; - int ok = 1; - int y; - const int pad = (format == RAW_YUV) ? 0 : 1; - const int uv_width = (width + 1) / 2; - const int uv_height = (height + 1) / 2; - const int out_stride = (width + pad) & ~pad; - const int a_height = yuv->a ? height : 0; - if (format == PGM) { - 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 (format == PGM) { - if (width & 1) fputc(0, fout); // padding byte - } - } - if (format == PGM) { // IMC4 layout - 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); - } - } else { - for (y = 0; ok && y < uv_height; ++y) { - ok &= (fwrite(yuv->u + y * yuv->u_stride, uv_width, 1, fout) == 1); - } - for (y = 0; ok && y < uv_height; ++y) { - 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 (format == PGM) { - if (width & 1) fputc(0, fout); // padding byte - } - } - return ok; -} static int SaveOutput(const WebPDecBuffer* const buffer, - OutputFileFormat format, const char* const out_file) { - FILE* fout = NULL; - int needs_open_file = 1; - const int use_stdout = !strcmp(out_file, "-"); + WebPOutputFileFormat format, const char* const out_file) { + const int use_stdout = (out_file != NULL) && !strcmp(out_file, "-"); int ok = 1; Stopwatch stop_watch; if (verbose) { StopwatchReset(&stop_watch); } + ok = WebPSaveImage(buffer, format, out_file); -#ifdef HAVE_WINCODEC_H - needs_open_file = (format != PNG); -#endif - - if (needs_open_file) { - fout = use_stdout ? ImgIoUtilSetBinaryMode(stdout) : fopen(out_file, "wb"); - if (fout == NULL) { - fprintf(stderr, "Error opening output file %s\n", out_file); - return 0; - } - } - - if (format == PNG || - format == RGBA || format == BGRA || format == ARGB || - format == rgbA || format == bgrA || format == Argb) { -#ifdef HAVE_WINCODEC_H - ok &= WritePNG(out_file, use_stdout, buffer); -#else - ok &= WritePNG(fout, buffer); -#endif - } else if (format == PAM) { - ok &= WritePPM(fout, buffer, 1); - } else if (format == PPM || format == RGB || format == BGR) { - ok &= WritePPM(fout, buffer, 0); - } else if (format == RGBA_4444 || format == RGB_565 || format == rgbA_4444) { - ok &= Write16bAsPGM(fout, buffer); - } else if (format == BMP) { - ok &= WriteBMP(fout, buffer); - } else if (format == TIFF) { - ok &= WriteTIFF(fout, buffer); - } else if (format == PGM || format == RAW_YUV || - format == YUV || format == YUVA) { - ok &= WritePGMOrYUV(fout, buffer, format == RAW_YUV ? RAW_YUV : PGM); - } else if (format == ALPHA_PLANE_ONLY) { - ok &= WriteAlphaPlane(fout, buffer); - } - if (fout != NULL && fout != stdout) { - fclose(fout); - } if (ok) { if (!quiet) { if (use_stdout) { @@ -600,7 +112,7 @@ static const char* const kFormatType[] = { }; static uint8_t* AllocateExternalBuffer(WebPDecoderConfig* config, - OutputFileFormat format, + WebPOutputFileFormat format, int use_external_memory) { uint8_t* external_buffer = NULL; WebPDecBuffer* const output_buffer = &config->output; @@ -671,7 +183,7 @@ int main(int argc, const char *argv[]) { WebPDecoderConfig config; WebPDecBuffer* const output_buffer = &config.output; WebPBitstreamFeatures* const bitstream = &config.input; - OutputFileFormat format = PNG; + WebPOutputFileFormat format = PNG; uint8_t* external_buffer = NULL; int use_external_memory = 0; const uint8_t* data = NULL; diff --git a/imageio/Android.mk b/imageio/Android.mk index c3f19e3e..4da7043c 100644 --- a/imageio/Android.mk +++ b/imageio/Android.mk @@ -34,3 +34,19 @@ LOCAL_C_INCLUDES := $(LOCAL_PATH)/../src LOCAL_MODULE := imagedec include $(BUILD_STATIC_LIBRARY) + +################################################################################ +# libimageenc + +include $(CLEAR_VARS) + +LOCAL_SRC_FILES := \ + image_enc.c \ + +LOCAL_CFLAGS := $(WEBP_CFLAGS) +LOCAL_C_INCLUDES := $(LOCAL_PATH)/../src +LOCAL_STATIC_LIBRARIES := imageio_util + +LOCAL_MODULE := imageenc + +include $(BUILD_STATIC_LIBRARY) diff --git a/imageio/Makefile.am b/imageio/Makefile.am index ff7002d9..acabe675 100644 --- a/imageio/Makefile.am +++ b/imageio/Makefile.am @@ -1,5 +1,5 @@ AM_CPPFLAGS += -I$(top_builddir)/src -I$(top_srcdir)/src -noinst_LTLIBRARIES = libimageio_util.la libimagedec.la +noinst_LTLIBRARIES = libimageio_util.la libimagedec.la libimageenc.la noinst_HEADERS = noinst_HEADERS += ../src/webp/decode.h @@ -17,3 +17,7 @@ libimagedec_la_SOURCES += webpdec.c webpdec.h libimagedec_la_SOURCES += wicdec.c wicdec.h libimagedec_la_CPPFLAGS = $(JPEG_INCLUDES) $(PNG_INCLUDES) $(TIFF_INCLUDES) libimagedec_la_CPPFLAGS += $(AM_CPPFLAGS) $(USE_EXPERIMENTAL_CODE) + +libimageenc_la_SOURCES = image_enc.c image_enc.h +libimageenc_la_CPPFLAGS = $(JPEG_INCLUDES) $(PNG_INCLUDES) $(TIFF_INCLUDES) +libimageenc_la_CPPFLAGS += $(AM_CPPFLAGS) $(USE_EXPERIMENTAL_CODE) diff --git a/imageio/image_enc.c b/imageio/image_enc.c new file mode 100644 index 00000000..8d70dc90 --- /dev/null +++ b/imageio/image_enc.c @@ -0,0 +1,558 @@ +// Copyright 2016 Google Inc. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the COPYING file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +// ----------------------------------------------------------------------------- +// +// Save image + +#include "./image_enc.h" + +#include +#include + +#ifdef WEBP_HAVE_PNG +#include +#include // note: this must be included *after* png.h +#endif + +#ifdef HAVE_WINCODEC_H +#ifdef __MINGW32__ +#define INITGUID // Without this GUIDs are declared extern and fail to link +#endif +#define CINTERFACE +#define COBJMACROS +#define _WIN32_IE 0x500 // Workaround bug in shlwapi.h when compiling C++ + // code with COBJMACROS. +#include // CreateStreamOnHGlobal() +#include +#include +#include +#endif + +#include "./imageio_util.h" + +//------------------------------------------------------------------------------ +// PNG + +#ifdef HAVE_WINCODEC_H + +#define IFS(fn) \ + do { \ + if (SUCCEEDED(hr)) { \ + hr = (fn); \ + if (FAILED(hr)) fprintf(stderr, #fn " failed %08lx\n", hr); \ + } \ + } while (0) + +#ifdef __cplusplus +#define MAKE_REFGUID(x) (x) +#else +#define MAKE_REFGUID(x) &(x) +#endif + +static HRESULT CreateOutputStream(const char* out_file_name, + int write_to_mem, IStream** stream) { + HRESULT hr = S_OK; + if (write_to_mem) { + // Output to a memory buffer. This is freed when 'stream' is released. + IFS(CreateStreamOnHGlobal(NULL, TRUE, stream)); + } else { + IFS(SHCreateStreamOnFileA(out_file_name, STGM_WRITE | STGM_CREATE, stream)); + } + if (FAILED(hr)) { + fprintf(stderr, "Error opening output file %s (%08lx)\n", + out_file_name, hr); + } + return hr; +} + +static HRESULT WriteUsingWIC(const char* out_file_name, int use_stdout, + REFGUID container_guid, + uint8_t* rgb, int stride, + uint32_t width, uint32_t height, int has_alpha) { + HRESULT hr = S_OK; + IWICImagingFactory* factory = NULL; + IWICBitmapFrameEncode* frame = NULL; + IWICBitmapEncoder* encoder = NULL; + IStream* stream = NULL; + WICPixelFormatGUID pixel_format = has_alpha ? GUID_WICPixelFormat32bppBGRA + : GUID_WICPixelFormat24bppBGR; + + if (out_file_name == NULL || rgb == NULL) return E_INVALIDARG; + + IFS(CoInitialize(NULL)); + IFS(CoCreateInstance(MAKE_REFGUID(CLSID_WICImagingFactory), NULL, + CLSCTX_INPROC_SERVER, + MAKE_REFGUID(IID_IWICImagingFactory), + (LPVOID*)&factory)); + if (hr == REGDB_E_CLASSNOTREG) { + fprintf(stderr, + "Couldn't access Windows Imaging Component (are you running " + "Windows XP SP3 or newer?). PNG support not available. " + "Use -ppm or -pgm for available PPM and PGM formats.\n"); + } + IFS(CreateOutputStream(out_file_name, use_stdout, &stream)); + IFS(IWICImagingFactory_CreateEncoder(factory, container_guid, NULL, + &encoder)); + IFS(IWICBitmapEncoder_Initialize(encoder, stream, + WICBitmapEncoderNoCache)); + IFS(IWICBitmapEncoder_CreateNewFrame(encoder, &frame, NULL)); + IFS(IWICBitmapFrameEncode_Initialize(frame, NULL)); + IFS(IWICBitmapFrameEncode_SetSize(frame, width, height)); + IFS(IWICBitmapFrameEncode_SetPixelFormat(frame, &pixel_format)); + IFS(IWICBitmapFrameEncode_WritePixels(frame, height, stride, + height * stride, rgb)); + IFS(IWICBitmapFrameEncode_Commit(frame)); + IFS(IWICBitmapEncoder_Commit(encoder)); + + if (SUCCEEDED(hr) && use_stdout) { + HGLOBAL image; + IFS(GetHGlobalFromStream(stream, &image)); + if (SUCCEEDED(hr)) { + HANDLE std_output = GetStdHandle(STD_OUTPUT_HANDLE); + DWORD mode; + const BOOL update_mode = GetConsoleMode(std_output, &mode); + const void* const image_mem = GlobalLock(image); + DWORD bytes_written = 0; + + // Clear output processing if necessary, then output the image. + if (update_mode) SetConsoleMode(std_output, 0); + if (!WriteFile(std_output, image_mem, (DWORD)GlobalSize(image), + &bytes_written, NULL) || + bytes_written != GlobalSize(image)) { + hr = E_FAIL; + } + if (update_mode) SetConsoleMode(std_output, mode); + GlobalUnlock(image); + } + } + + if (frame != NULL) IUnknown_Release(frame); + if (encoder != NULL) IUnknown_Release(encoder); + if (factory != NULL) IUnknown_Release(factory); + if (stream != NULL) IUnknown_Release(stream); + return hr; +} + +int WebPWritePNG(const char* out_file_name, int use_stdout, + const WebPDecBuffer* const buffer) { + const uint32_t width = buffer->width; + const uint32_t height = buffer->height; + uint8_t* const rgb = buffer->u.RGBA.rgba; + const int stride = buffer->u.RGBA.stride; + const int has_alpha = WebPIsAlphaMode(buffer->colorspace); + + return SUCCEEDED(WriteUsingWIC(out_file_name, use_stdout, + MAKE_REFGUID(GUID_ContainerFormatPng), + rgb, stride, width, height, has_alpha)); +} + +#elif defined(WEBP_HAVE_PNG) // !HAVE_WINCODEC_H +static void PNGAPI PNGErrorFunction(png_structp png, png_const_charp dummy) { + (void)dummy; // remove variable-unused warning + longjmp(png_jmpbuf(png), 1); +} + +int WebPWritePNG(FILE* out_file, const WebPDecBuffer* const buffer) { + const uint32_t width = buffer->width; + const uint32_t height = buffer->height; + uint8_t* const rgb = buffer->u.RGBA.rgba; + const int stride = buffer->u.RGBA.stride; + const int has_alpha = WebPIsAlphaMode(buffer->colorspace); + volatile png_structp png; + volatile png_infop info; + png_uint_32 y; + + if (out_file == NULL || buffer == NULL) return 0; + + png = png_create_write_struct(PNG_LIBPNG_VER_STRING, + NULL, PNGErrorFunction, NULL); + if (png == NULL) { + return 0; + } + info = png_create_info_struct(png); + if (info == NULL) { + png_destroy_write_struct((png_structpp)&png, NULL); + return 0; + } + if (setjmp(png_jmpbuf(png))) { + png_destroy_write_struct((png_structpp)&png, (png_infopp)&info); + return 0; + } + png_init_io(png, out_file); + png_set_IHDR(png, info, width, height, 8, + has_alpha ? PNG_COLOR_TYPE_RGBA : PNG_COLOR_TYPE_RGB, + PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT, + PNG_FILTER_TYPE_DEFAULT); + png_write_info(png, info); + for (y = 0; y < height; ++y) { + png_bytep row = rgb + y * stride; + png_write_rows(png, &row, 1); + } + png_write_end(png, info); + png_destroy_write_struct((png_structpp)&png, (png_infopp)&info); + return 1; +} +#else // !HAVE_WINCODEC_H && !WEBP_HAVE_PNG +int WebPWritePNG(FILE* fout, const WebPDecBuffer* const buffer) { + if (fout == NULL || buffer == NULL) return 0; + + fprintf(stderr, "PNG support not compiled. Please install the libpng " + "development package before building.\n"); + fprintf(stderr, "You can run with -ppm flag to decode in PPM format.\n"); + return 0; +} +#endif + +//------------------------------------------------------------------------------ +// PPM / PAM + +static int WritePPMPAM(FILE* fout, const WebPDecBuffer* const buffer, + int alpha) { + const uint32_t width = buffer->width; + const uint32_t height = buffer->height; + const uint8_t* const rgb = buffer->u.RGBA.rgba; + const int stride = buffer->u.RGBA.stride; + const size_t bytes_per_px = alpha ? 4 : 3; + uint32_t y; + + if (fout == NULL || buffer == NULL || rgb == NULL) return 0; + + if (alpha) { + fprintf(fout, "P7\nWIDTH %u\nHEIGHT %u\nDEPTH 4\nMAXVAL 255\n" + "TUPLTYPE RGB_ALPHA\nENDHDR\n", width, height); + } else { + fprintf(fout, "P6\n%u %u\n255\n", width, height); + } + for (y = 0; y < height; ++y) { + if (fwrite(rgb + y * stride, width, bytes_per_px, fout) != bytes_per_px) { + return 0; + } + } + return 1; +} + +int WebPWritePPM(FILE* fout, const WebPDecBuffer* const buffer) { + return WritePPMPAM(fout, buffer, 0); +} + +int WebPWritePAM(FILE* fout, const WebPDecBuffer* const buffer) { + return WritePPMPAM(fout, buffer, 1); +} + +//------------------------------------------------------------------------------ +// Raw PGM + +// Save 16b mode (RGBA4444, RGB565, ...) for debugging purpose. +int WebPWrite16bAsPGM(FILE* fout, const WebPDecBuffer* const buffer) { + 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 = 2; + uint32_t y; + + if (fout == NULL || buffer == NULL || rgba == NULL) return 0; + + fprintf(fout, "P5\n%u %u\n255\n", width * bytes_per_px, height); + for (y = 0; y < height; ++y) { + if (fwrite(rgba + y * stride, width, bytes_per_px, fout) != bytes_per_px) { + return 0; + } + } + return 1; +} + +//------------------------------------------------------------------------------ +// BMP + +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 +int WebPWriteBMP(FILE* fout, const WebPDecBuffer* const buffer) { + const int has_alpha = WebPIsAlphaMode(buffer->colorspace); + 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 }; + + if (fout == NULL || buffer == NULL || rgba == NULL) return 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 + +//------------------------------------------------------------------------------ +// TIFF + +#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) + +int WebPWriteTIFF(FILE* fout, const WebPDecBuffer* const buffer) { + const int has_alpha = WebPIsAlphaMode(buffer->colorspace); + 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; + + if (fout == NULL || buffer == NULL || rgba == NULL) return 0; + + // 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 + +//------------------------------------------------------------------------------ +// Raw Alpha + +int WebPWriteAlphaPlane(FILE* fout, const WebPDecBuffer* const buffer) { + const uint32_t width = buffer->width; + const uint32_t height = buffer->height; + const uint8_t* const a = buffer->u.YUVA.a; + const int a_stride = buffer->u.YUVA.a_stride; + uint32_t y; + + if (fout == NULL || buffer == NULL || a == NULL) return 0; + + fprintf(fout, "P5\n%u %u\n255\n", width, height); + for (y = 0; y < height; ++y) { + if (fwrite(a + y * a_stride, width, 1, fout) != 1) { + return 0; + } + } + return 1; +} + +//------------------------------------------------------------------------------ +// PGM with IMC4 layout + +int WebPWritePGM(FILE* fout, const WebPDecBuffer* const buffer) { + const int width = buffer->width; + const int height = buffer->height; + const WebPYUVABuffer* const yuv = &buffer->u.YUVA; + const int uv_width = (width + 1) / 2; + const int uv_height = (height + 1) / 2; + const int a_height = (yuv->a != NULL) ? height : 0; + int ok = 1; + int y; + + if (fout == NULL || buffer == NULL) return 0; + if (yuv->y == NULL || yuv->u == NULL || yuv->v == NULL) return 0; + + fprintf(fout, "P5\n%d %d\n255\n", + (width + 1) & ~1, 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; +} + +//------------------------------------------------------------------------------ +// Raw YUV(A) planes + +int WebPWriteYUV(FILE* fout, const WebPDecBuffer* const buffer) { + const int width = buffer->width; + const int height = buffer->height; + const WebPYUVABuffer* const yuv = &buffer->u.YUVA; + const int uv_width = (width + 1) / 2; + const int uv_height = (height + 1) / 2; + const int a_height = (yuv->a != NULL) ? height : 0; + int ok = 1; + int y; + + if (fout == NULL || buffer == NULL) return 0; + if (yuv->y == NULL || yuv->u == NULL || yuv->v == NULL) return 0; + + for (y = 0; ok && y < height; ++y) { + ok &= (fwrite(yuv->y + y * yuv->y_stride, width, 1, fout) == 1); + } + for (y = 0; ok && y < uv_height; ++y) { + ok &= (fwrite(yuv->u + y * yuv->u_stride, uv_width, 1, fout) == 1); + } + for (y = 0; ok && y < uv_height; ++y) { + 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); + } + return ok; +} + +//------------------------------------------------------------------------------ +// Generic top-level call + +int WebPSaveImage(const WebPDecBuffer* const buffer, + WebPOutputFileFormat format, const char* const out_file) { + FILE* fout = NULL; + int needs_open_file = 1; + const int use_stdout = (out_file != NULL) && !strcmp(out_file, "-"); + int ok = 1; + + if (buffer == NULL || out_file == NULL) return 0; + +#ifdef HAVE_WINCODEC_H + needs_open_file = (format != PNG); +#endif + + if (needs_open_file) { + fout = use_stdout ? ImgIoUtilSetBinaryMode(stdout) : fopen(out_file, "wb"); + if (fout == NULL) { + fprintf(stderr, "Error opening output file %s\n", out_file); + return 0; + } + } + + if (format == PNG || + format == RGBA || format == BGRA || format == ARGB || + format == rgbA || format == bgrA || format == Argb) { +#ifdef HAVE_WINCODEC_H + ok &= WebPWritePNG(out_file, use_stdout, buffer); +#else + ok &= WebPWritePNG(fout, buffer); +#endif + } else if (format == PAM) { + ok &= WebPWritePAM(fout, buffer); + } else if (format == PPM || format == RGB || format == BGR) { + ok &= WebPWritePPM(fout, buffer); + } else if (format == RGBA_4444 || format == RGB_565 || format == rgbA_4444) { + ok &= WebPWrite16bAsPGM(fout, buffer); + } else if (format == BMP) { + ok &= WebPWriteBMP(fout, buffer); + } else if (format == TIFF) { + ok &= WebPWriteTIFF(fout, buffer); + } else if (format == RAW_YUV) { + ok &= WebPWriteYUV(fout, buffer); + } else if (format == PGM || format == YUV || format == YUVA) { + ok &= WebPWritePGM(fout, buffer); + } else if (format == ALPHA_PLANE_ONLY) { + ok &= WebPWriteAlphaPlane(fout, buffer); + } + if (fout != NULL && fout != stdout) { + fclose(fout); + } + return ok; +} diff --git a/imageio/image_enc.h b/imageio/image_enc.h new file mode 100644 index 00000000..f8abdaca --- /dev/null +++ b/imageio/image_enc.h @@ -0,0 +1,96 @@ +// Copyright 2016 Google Inc. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the COPYING file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +// ----------------------------------------------------------------------------- +// +// All-in-one library to save PNG/JPEG/WebP/TIFF/WIC images. +// +// Author: Skal (pascal.massimino@gmail.com) + +#ifndef WEBP_IMAGEIO_IMAGE_ENC_H_ +#define WEBP_IMAGEIO_IMAGE_ENC_H_ + +#include + +#ifdef HAVE_CONFIG_H +#include "webp/config.h" +#endif + +#include "webp/types.h" +#include "webp/decode.h" + +#ifdef __cplusplus +extern "C" { +#endif + +// Output types +typedef enum { + PNG = 0, + PAM, + PPM, + PGM, + BMP, + TIFF, + RAW_YUV, + ALPHA_PLANE_ONLY, // this is for experimenting only + // forced colorspace output (for testing, mostly) + RGB, RGBA, BGR, BGRA, ARGB, + RGBA_4444, RGB_565, + rgbA, bgrA, Argb, rgbA_4444, + YUV, YUVA +} WebPOutputFileFormat; + +// General all-purpose call. +// Most formats expect a 'buffer' containing RGBA-like samples, except +// RAW_YUV, YUV and YUVA formats. +// If 'out_file_name' is "-", data is saved to stdout. +// Returns false if an error occurred, true otherwise. +int WebPSaveImage(const WebPDecBuffer* const buffer, + WebPOutputFileFormat format, const char* const out_file_name); + +// Save to PNG. +#ifdef HAVE_WINCODEC_H +int WebPWritePNG(const char* out_file_name, int use_stdout, + const struct WebPDecBuffer* const buffer); +#else +int WebPWritePNG(FILE* out_file, const WebPDecBuffer* const buffer); +#endif + +// Save to PPM format (RGB, no alpha) +int WebPWritePPM(FILE* fout, const struct WebPDecBuffer* const buffer); + +// Save to PAM format (= PPM + alpha) +int WebPWritePAM(FILE* fout, const struct WebPDecBuffer* const buffer); + +// Save 16b mode (RGBA4444, RGB565, ...) for debugging purposes. +int WebPWrite16bAsPGM(FILE* fout, const struct WebPDecBuffer* const buffer); + +// Save as BMP +int WebPWriteBMP(FILE* fout, const struct WebPDecBuffer* const buffer); + +// Save as TIFF +int WebPWriteTIFF(FILE* fout, const struct WebPDecBuffer* const buffer); + +// Save the ALPHA plane (only) as a PGM +int WebPWriteAlphaPlane(FILE* fout, const struct WebPDecBuffer* const buffer); + +// Save as YUV samples as PGM format (using IMC4 layout). +// See: http://www.fourcc.org/yuv.php#IMC4. +// (very convenient format for viewing the samples, esp. for odd dimensions). +int WebPWritePGM(FILE* fout, const struct WebPDecBuffer* const buffer); + +// Save YUV(A) planes sequentially (raw dump) +int WebPWriteYUV(FILE* fout, const struct WebPDecBuffer* const buffer); + +// Save 16b mode (RGBA4444, RGB565, ...) as PGM format, for debugging purposes. +int WebPWrite16bAsPGM(FILE* fout, const struct WebPDecBuffer* const buffer); + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif // WEBP_IMAGEIO_IMAGE_ENC_H_ diff --git a/makefile.unix b/makefile.unix index e1ed3a61..ad21679e 100644 --- a/makefile.unix +++ b/makefile.unix @@ -223,6 +223,9 @@ EX_FORMAT_DEC_OBJS = \ imageio/tiffdec.o \ imageio/webpdec.o \ +EX_FORMAT_ENC_OBJS = \ + imageio/image_enc.o \ + EX_UTIL_OBJS = \ examples/example_util.o \ @@ -311,8 +314,11 @@ HDRS = \ src/webp/format_constants.h \ $(HDRS_INSTALLED) \ -OUT_LIBS = examples/libexample_util.a imageio/libimageio_util.a -OUT_LIBS += imageio/libimagedec.a src/libwebpdecoder.a +OUT_LIBS = examples/libexample_util.a +OUT_LIBS += imageio/libimageio_util.a +OUT_LIBS += imageio/libimagedec.a +OUT_LIBS += imageio/libimageenc.a +OUT_LIBS += src/libwebpdecoder.a OUT_LIBS += src/libwebp.a EXTRA_LIB = extras/libwebpextras.a OUT_EXAMPLES = examples/cwebp examples/dwebp @@ -349,6 +355,7 @@ examples/libexample_util.a: $(EX_UTIL_OBJS) examples/libgifdec.a: $(GIFDEC_OBJS) extras/libwebpextras.a: $(LIBWEBPEXTRA_OBJS) imageio/libimagedec.a: $(EX_FORMAT_DEC_OBJS) +imageio/libimageenc.a: $(EX_FORMAT_ENC_OBJS) imageio/libimageio_util.a: $(IMAGE_UTIL_OBJS) src/libwebpdecoder.a: $(LIBWEBPDECODER_OBJS) src/libwebp.a: $(LIBWEBP_OBJS) @@ -377,6 +384,7 @@ examples/cwebp: src/libwebp.a examples/cwebp: EXTRA_LIBS += $(CWEBP_LIBS) examples/dwebp: examples/libexample_util.a examples/dwebp: imageio/libimagedec.a +examples/dwebp: imageio/libimageenc.a examples/dwebp: imageio/libimageio_util.a examples/dwebp: src/libwebp.a examples/dwebp: EXTRA_LIBS += $(DWEBP_LIBS)