diff options
-rw-r--r-- | samples/pdfium_test.cc | 90 | ||||
-rw-r--r-- | testing/image_diff/image_diff_png.cpp | 65 | ||||
-rw-r--r-- | testing/image_diff/image_diff_png.h | 14 |
3 files changed, 163 insertions, 6 deletions
diff --git a/samples/pdfium_test.cc b/samples/pdfium_test.cc index ee95e2f3c1..f8096abc50 100644 --- a/samples/pdfium_test.cc +++ b/samples/pdfium_test.cc @@ -88,6 +88,7 @@ struct Options { send_events(false), render_oneshot(false), save_attachments(false), + save_images(false), #ifdef ENABLE_CALLGRIND callgrind_delimiters(false), #endif // ENABLE_CALLGRIND @@ -101,6 +102,7 @@ struct Options { bool send_events; bool render_oneshot; bool save_attachments; + bool save_images; #ifdef ENABLE_CALLGRIND bool callgrind_delimiters; #endif // ENABLE_CALLGRIND @@ -727,6 +729,8 @@ bool ParseCommandLine(const std::vector<std::string>& args, options->render_oneshot = true; } else if (cur_arg == "--save-attachments") { options->save_attachments = true; + } else if (cur_arg == "--save-images") { + options->save_images = true; #ifdef ENABLE_CALLGRIND } else if (cur_arg == "--callgrind-delim") { options->callgrind_delimiters = true; @@ -1117,6 +1121,88 @@ void SaveAttachments(FPDF_DOCUMENT doc, const std::string& name) { } } +void SaveImages(FPDF_PAGE page, const char* pdf_name, int page_num) { + for (int i = 0; i < FPDFPage_CountObject(page); ++i) { + FPDF_PAGEOBJECT obj = FPDFPage_GetObject(page, i); + if (FPDFPageObj_GetType(obj) != FPDF_PAGEOBJ_IMAGE) + continue; + + std::unique_ptr<void, FPDFBitmapDeleter> bitmap( + FPDFImageObj_GetBitmap(obj)); + if (!bitmap) { + fprintf(stderr, "Image object #%d on page #%d has an empty bitmap.\n", + i + 1, page_num + 1); + continue; + } + + int format = FPDFBitmap_GetFormat(bitmap.get()); + if (format == FPDFBitmap_Unknown) { + fprintf(stderr, + "Image object #%d on page #%d has a bitmap of unknown format.\n", + i + 1, page_num + 1); + continue; + } + + std::vector<unsigned char> png_encoding; + const unsigned char* buffer = + static_cast<const unsigned char*>(FPDFBitmap_GetBuffer(bitmap.get())); + int width = FPDFBitmap_GetWidth(bitmap.get()); + int height = FPDFBitmap_GetHeight(bitmap.get()); + int stride = FPDFBitmap_GetStride(bitmap.get()); + bool ret = false; + switch (format) { + case FPDFBitmap_Gray: + ret = image_diff_png::EncodeGrayPNG(buffer, width, height, stride, + &png_encoding); + break; + case FPDFBitmap_BGR: + ret = image_diff_png::EncodeBGRPNG(buffer, width, height, stride, + &png_encoding); + break; + case FPDFBitmap_BGRx: + ret = image_diff_png::EncodeBGRAPNG(buffer, width, height, stride, true, + &png_encoding); + break; + case FPDFBitmap_BGRA: + ret = image_diff_png::EncodeBGRAPNG(buffer, width, height, stride, + false, &png_encoding); + break; + default: + NOTREACHED(); + } + if (!ret) { + fprintf(stderr, + "Failed to convert image object #%d on page #%d to png.\n", i + 1, + page_num + 1); + continue; + } + + char filename[256]; + int chars_formatted = snprintf(filename, sizeof(filename), "%s.%d.%d.png", + pdf_name, page_num, i); + if (chars_formatted < 0 || + static_cast<size_t>(chars_formatted) >= sizeof(filename)) { + fprintf(stderr, "Filename %s for saving image is too long\n", filename); + continue; + } + + FILE* fp = fopen(filename, "wb"); + if (!fp) { + fprintf(stderr, "Failed to open %s for saving image.\n", filename); + continue; + } + + size_t bytes_written = + fwrite(&png_encoding.front(), 1, png_encoding.size(), fp); + if (bytes_written != png_encoding.size()) + fprintf(stderr, "Failed to write to %s.\n", filename); + else + fprintf(stderr, "Successfully wrote embedded image %s.\n", filename); + + (void)fclose(fp); + } +} + // Note, for a client using progressive rendering you'd want to determine if you // need the rendering to pause instead of always saying |true|. This is for // testing to force the renderer to break whenever possible. @@ -1136,6 +1222,8 @@ bool RenderPage(const std::string& name, return false; if (options.send_events) SendPageEvents(form, page, events); + if (options.save_images) + SaveImages(page, name.c_str(), page_index); if (options.output_format == OUTPUT_STRUCTURE) { DumpPageStructure(page, page_index); return true; @@ -1413,6 +1501,8 @@ constexpr char kUsageString[] = " --render-oneshot - render image without using progressive renderer\n" " --save-attachments - write embedded attachments " "<pdf-name>.attachment.<attachment-name>\n" + " --save-images - write embedded images " + "<pdf-name>.<page-number>.<object-number>.png\n" #ifdef ENABLE_CALLGRIND " --callgrind-delim - delimit interesting section when using callgrind\n" #endif // ENABLE_CALLGRIND diff --git a/testing/image_diff/image_diff_png.cpp b/testing/image_diff/image_diff_png.cpp index a5e8cdb101..56be539057 100644 --- a/testing/image_diff/image_diff_png.cpp +++ b/testing/image_diff/image_diff_png.cpp @@ -29,12 +29,18 @@ enum ColorFormat { // This is the native JPEG format. FORMAT_RGB, + // 3 bytes per pixel, in BGR order regardless of endianness. + FORMAT_BGR, + // 4 bytes per pixel, in RGBA order in memory regardless of endianness. FORMAT_RGBA, // 4 bytes per pixel, in BGRA order in memory regardless of endianness. // This is the default Windows DIB order. FORMAT_BGRA, + + // 1 byte per pixel. + FORMAT_GRAY, }; // Represents a comment in the tEXt ancillary chunk of the png. @@ -58,6 +64,19 @@ void ConvertBetweenBGRAandRGBA(const unsigned char* input, } } +void ConvertBGRtoRGB(const unsigned char* bgr, + int pixel_width, + unsigned char* rgb, + bool* is_opaque) { + for (int x = 0; x < pixel_width; x++) { + const unsigned char* pixel_in = &bgr[x * 3]; + unsigned char* pixel_out = &rgb[x * 3]; + pixel_out[0] = pixel_in[2]; + pixel_out[1] = pixel_in[1]; + pixel_out[2] = pixel_in[0]; + } +} + void ConvertRGBAtoRGB(const unsigned char* rgba, int pixel_width, unsigned char* rgb, @@ -93,7 +112,7 @@ class PngDecoderState { output_channels(0), is_opaque(true), output(o), - row_converter(NULL), + row_converter(nullptr), width(0), height(0), done(false) {} @@ -217,7 +236,7 @@ void DecodeInfoCallback(png_struct* png_ptr, png_info* info_ptr) { if (channels == 3) { switch (state->output_format) { case FORMAT_RGB: - state->row_converter = NULL; // no conversion necessary + state->row_converter = nullptr; // no conversion necessary state->output_channels = 3; break; case FORMAT_RGBA: @@ -228,6 +247,10 @@ void DecodeInfoCallback(png_struct* png_ptr, png_info* info_ptr) { state->row_converter = &ConvertRGBtoBGRA; state->output_channels = 4; break; + case FORMAT_GRAY: + state->row_converter = nullptr; + state->output_channels = 1; + break; default: NOTREACHED(); break; @@ -239,7 +262,7 @@ void DecodeInfoCallback(png_struct* png_ptr, png_info* info_ptr) { state->output_channels = 3; break; case FORMAT_RGBA: - state->row_converter = NULL; // no conversion necessary + state->row_converter = nullptr; // no conversion necessary state->output_channels = 4; break; case FORMAT_BGRA: @@ -546,11 +569,14 @@ bool EncodeWithCompressionLevel(const unsigned char* input, std::vector<unsigned char>* output) { // Run to convert an input row into the output row format, NULL means no // conversion is necessary. - FormatConverter converter = NULL; + FormatConverter converter = nullptr; int input_color_components, output_color_components; int png_output_color_type; switch (format) { + case FORMAT_BGR: + converter = ConvertBGRtoRGB; + case FORMAT_RGB: input_color_components = 3; output_color_components = 3; @@ -567,7 +593,7 @@ bool EncodeWithCompressionLevel(const unsigned char* input, } else { output_color_components = 4; png_output_color_type = PNG_COLOR_TYPE_RGB_ALPHA; - converter = NULL; + converter = nullptr; } break; @@ -584,13 +610,20 @@ bool EncodeWithCompressionLevel(const unsigned char* input, } break; + case FORMAT_GRAY: + input_color_components = 1; + output_color_components = 1; + png_output_color_type = PNG_COLOR_TYPE_GRAY; + discard_transparency = false; + break; + default: NOTREACHED(); return false; } // Row stride should be at least as long as the length of the data. - if (input_color_components * width < row_byte_width) + if (row_byte_width < input_color_components * width) return false; png_struct* png_ptr = @@ -636,6 +669,16 @@ bool DecodePNG(const unsigned char* input, return Decode(input, input_size, FORMAT_RGBA, output, width, height); } +// Encode a BGR pixel array into a PNG. +bool EncodeBGRPNG(const unsigned char* input, + int width, + int height, + int row_byte_width, + std::vector<unsigned char>* output) { + return Encode(input, FORMAT_BGR, width, height, row_byte_width, false, + std::vector<Comment>(), output); +} + // Encode an RGBA pixel array into a PNG. bool EncodeRGBAPNG(const unsigned char* input, int width, @@ -657,4 +700,14 @@ bool EncodeBGRAPNG(const unsigned char* input, discard_transparency, std::vector<Comment>(), output); } +// Encode a grayscale pixel array into a PNG. +bool EncodeGrayPNG(const unsigned char* input, + int width, + int height, + int row_byte_width, + std::vector<unsigned char>* output) { + return Encode(input, FORMAT_GRAY, width, height, row_byte_width, false, + std::vector<Comment>(), output); +} + } // namespace image_diff_png diff --git a/testing/image_diff/image_diff_png.h b/testing/image_diff/image_diff_png.h index 4d87aa1cc0..b334b20453 100644 --- a/testing/image_diff/image_diff_png.h +++ b/testing/image_diff/image_diff_png.h @@ -18,6 +18,13 @@ bool DecodePNG(const unsigned char* input, int* width, int* height); +// Encode a BGR pixel array into a PNG. +bool EncodeBGRPNG(const unsigned char* input, + int width, + int height, + int row_byte_width, + std::vector<unsigned char>* output); + // Encode an RGBA pixel array into a PNG. bool EncodeRGBAPNG(const unsigned char* input, int width, @@ -33,6 +40,13 @@ bool EncodeBGRAPNG(const unsigned char* input, bool discard_transparency, std::vector<unsigned char>* output); +// Encode a grayscale pixel array into a PNG. +bool EncodeGrayPNG(const unsigned char* input, + int width, + int height, + int row_byte_width, + std::vector<unsigned char>* output); + } // namespace image_diff_png #endif // TESTING_IMAGE_DIFF_IMAGE_DIFF_PNG_H_ |