From 3e0507ba6c823f1ffd6bbefde3376a30ee03e535 Mon Sep 17 00:00:00 2001 From: Michael R Sweet Date: Sun, 18 Jul 2021 09:23:39 -0400 Subject: [PATCH] Rework pdfioFileCreateImageObjFromData to have a separate alpha argument so that CMYK images can be supported. Add unit tests. --- pdfio-content.c | 82 ++++++++++++-------- pdfio-content.h | 2 +- testpdfio.c | 196 ++++++++++++++++++++++++++++++++++++++++++++++-- 3 files changed, 241 insertions(+), 39 deletions(-) diff --git a/pdfio-content.c b/pdfio-content.c index 6aa8c4a..e7c5903 100644 --- a/pdfio-content.c +++ b/pdfio-content.c @@ -1641,8 +1641,9 @@ pdfioFileCreateImageObjFromData( const unsigned char *data, // I - Pointer to image data size_t width, // I - Width of image size_t height, // I - Height of image - int num_colors, // I - Number of colors + size_t num_colors, // I - Number of colors pdfio_array_t *color_data, // I - Colorspace data or `NULL` for default + bool alpha, // I - `true` if data contains an alpha channel bool interpolate) // I - Interpolate image data? { pdfio_dict_t *dict, // Image dictionary @@ -1652,24 +1653,34 @@ pdfioFileCreateImageObjFromData( // Mask image object, if any pdfio_stream_t *st; // Image stream size_t x, y, // X and Y position in image + bpp, // Bytes per pixel linelen; // Line length const unsigned char *dataptr; // Pointer into image data unsigned char *line = NULL, // Current line *lineptr; // Pointer into line + static const char *defcolors[] = // Default ColorSpace values + { + NULL, + "DeviceGray", + NULL, + "DeviceRGB", + "DeviceCMYK" + }; // Range check input... - if (!pdf || !data || !width || !height || num_colors < 1 || num_colors > 4) + if (!pdf || !data || !width || !height || num_colors < 1 || num_colors == 2 || num_colors > 4) return (NULL); // Allocate memory for one line of data... - linelen = (num_colors < 3 ? 1 : 3) * width; + bpp = alpha ? num_colors + 1 : num_colors; + linelen = num_colors * width; if ((line = malloc(linelen)) == NULL) return (NULL); // Generate a mask image, as needed... - if (!(num_colors & 1)) + if (alpha) { // Create the image mask dictionary... if ((dict = pdfioDictCreate(pdf)) == NULL) @@ -1711,9 +1722,9 @@ pdfioFileCreateImageObjFromData( return (NULL); } - for (y = height, dataptr = data + num_colors - 1; y > 0; y --) + for (y = height, dataptr = data + num_colors; y > 0; y --) { - for (x = width, lineptr = line; x > 0; x --, dataptr += num_colors) + for (x = width, lineptr = line; x > 0; x --, dataptr += bpp) *lineptr++ = *dataptr; pdfioStreamWrite(st, line, width); @@ -1739,7 +1750,7 @@ pdfioFileCreateImageObjFromData( if (color_data) pdfioDictSetArray(dict, "ColorSpace", color_data); else - pdfioDictSetArray(dict, "ColorSpace", pdfioArrayCreateColorFromMatrix(pdf, num_colors < 3 ? 1 : 3, pdfioSRGBGamma, pdfioSRGBMatrix, pdfioSRGBWhitePoint)); + pdfioDictSetName(dict, "ColorSpace", defcolors[num_colors]); if (mask_obj) pdfioDictSetObj(dict, "SMask", mask_obj); @@ -1751,7 +1762,7 @@ pdfioFileCreateImageObjFromData( } pdfioDictSetNumber(decode, "BitsPerComponent", 8); - pdfioDictSetNumber(decode, "Colors", num_colors < 3 ? 1 : 3); + pdfioDictSetNumber(decode, "Colors", num_colors); pdfioDictSetNumber(decode, "Columns", width); pdfioDictSetNumber(decode, "Predictor", _PDFIO_PREDICTOR_PNG_AUTO); pdfioDictSetDict(dict, "DecodeParms", decode); @@ -1771,30 +1782,39 @@ pdfioFileCreateImageObjFromData( for (y = height, dataptr = data; y > 0; y --) { - switch (num_colors) + if (alpha) { - case 1 : - pdfioStreamWrite(st, dataptr, linelen); - dataptr += linelen; - break; - case 2 : - for (x = width, lineptr = line; x > 0; x --, dataptr += num_colors) - *lineptr++ = *dataptr; - pdfioStreamWrite(st, line, linelen); - break; - case 3 : - pdfioStreamWrite(st, dataptr, linelen); - dataptr += linelen; - break; - case 4 : - for (x = width, lineptr = line; x > 0; x --, dataptr += num_colors) - { - *lineptr++ = dataptr[0]; - *lineptr++ = dataptr[1]; - *lineptr++ = dataptr[2]; - } - pdfioStreamWrite(st, line, linelen); - break; + switch (num_colors) + { + case 1 : + for (x = width, lineptr = line; x > 0; x --, dataptr += bpp) + *lineptr++ = *dataptr; + break; + case 3 : + for (x = width, lineptr = line; x > 0; x --, dataptr += bpp) + { + *lineptr++ = dataptr[0]; + *lineptr++ = dataptr[1]; + *lineptr++ = dataptr[2]; + } + break; + case 4 : + for (x = width, lineptr = line; x > 0; x --, dataptr += bpp) + { + *lineptr++ = dataptr[0]; + *lineptr++ = dataptr[1]; + *lineptr++ = dataptr[2]; + *lineptr++ = dataptr[3]; + } + break; + } + + pdfioStreamWrite(st, line, linelen); + } + else + { + pdfioStreamWrite(st, dataptr, linelen); + dataptr += linelen; } } diff --git a/pdfio-content.h b/pdfio-content.h index c83f7c2..b23e7fd 100644 --- a/pdfio-content.h +++ b/pdfio-content.h @@ -147,7 +147,7 @@ extern bool pdfioContentTextShowJustified(pdfio_stream_t *st, bool unicode, siz extern pdfio_obj_t *pdfioFileCreateFontObjFromBase(pdfio_file_t *pdf, const char *name) PDFIO_PUBLIC; extern pdfio_obj_t *pdfioFileCreateFontObjFromFile(pdfio_file_t *pdf, const char *filename, bool unicode) PDFIO_PUBLIC; extern pdfio_obj_t *pdfioFileCreateICCObjFromFile(pdfio_file_t *pdf, const char *filename, size_t num_colors) PDFIO_PUBLIC; -extern pdfio_obj_t *pdfioFileCreateImageObjFromData(pdfio_file_t *pdf, const unsigned char *data, size_t width, size_t height, int num_colors, pdfio_array_t *color_data, bool interpolate) PDFIO_PUBLIC; +extern pdfio_obj_t *pdfioFileCreateImageObjFromData(pdfio_file_t *pdf, const unsigned char *data, size_t width, size_t height, size_t num_colors, pdfio_array_t *color_data, bool alpha, bool interpolate) PDFIO_PUBLIC; extern pdfio_obj_t *pdfioFileCreateImageObjFromFile(pdfio_file_t *pdf, const char *filename, bool interpolate) PDFIO_PUBLIC; // Image object helpers... diff --git a/testpdfio.c b/testpdfio.c index 690b2eb..ba799b0 100644 --- a/testpdfio.c +++ b/testpdfio.c @@ -33,6 +33,7 @@ static bool error_cb(pdfio_file_t *pdf, const char *message, bool *error); static ssize_t token_consume_cb(const char **s, size_t bytes); static ssize_t token_peek_cb(const char **s, char *buffer, size_t bytes); static int verify_image(pdfio_file_t *pdf, size_t number); +static int write_alpha_test(pdfio_file_t *pdf, int number, pdfio_obj_t *font); static int write_color_patch(pdfio_stream_t *st, bool device); static int write_color_test(pdfio_file_t *pdf, int number, pdfio_obj_t *font); static int write_font_test(pdfio_file_t *pdf, int number, pdfio_obj_t *font, bool unicode); @@ -417,15 +418,19 @@ do_unit_tests(void) if (write_images_test(outpdf, 7, helvetica)) return (1); - // Test TrueType fonts... - if (write_font_test(outpdf, 8, helvetica, false)) + // Write a page width alpha (soft masks)... + if (write_alpha_test(outpdf, 8, helvetica)) return (1); - if (write_font_test(outpdf, 9, helvetica, true)) + // Test TrueType fonts... + if (write_font_test(outpdf, 9, helvetica, false)) + return (1); + + if (write_font_test(outpdf, 10, helvetica, true)) return (1); // Print this text file... - if (write_text_test(outpdf, 10, helvetica, "README.md")) + if (write_text_test(outpdf, 11, helvetica, "README.md")) return (1); // Close the test PDF file... @@ -723,6 +728,182 @@ verify_image(pdfio_file_t *pdf, // I - PDF file } +// +// 'write_alpha_test()' - Write a series of test images with alpha channels. +// + +static int // O - 1 on failure, 0 on success +write_alpha_test( + pdfio_file_t *pdf, // I - PDF file + int number, // I - Page number + pdfio_obj_t *font) // I - Text font +{ + pdfio_dict_t *dict; // Page dictionary + pdfio_stream_t *st; // Page stream + pdfio_obj_t *images[6]; // Images using PNG predictors + char iname[32]; // Image name + int i, // Image number + x, y; // Coordinates in image + unsigned char buffer[1280 * 256], // Buffer for image + *bufptr; // Pointer into buffer + + + // Create the images... + for (i = 0; i < 6; i ++) + { + size_t num_colors = 0; // Number of colors + + // Generate test image data... + switch (i) + { + case 0 : // Grayscale + case 3 : // Grayscale + alpha + num_colors = 1; + for (y = 0, bufptr = buffer; y < 256; y ++) + { + for (x = 0; x < 256; x ++) + { + unsigned char r = (unsigned char)y; + unsigned char g = (unsigned char)(y + x); + unsigned char b = (unsigned char)(y - x); + + *bufptr++ = (unsigned char)((r * 30 + g * 59 + b * 11) / 100); + + if (i > 2) + { + // Add alpha channel + *bufptr++ = (unsigned char)((x + y) / 2); + } + } + } + break; + + case 1 : // RGB + case 4 : // RGB + alpha + num_colors = 3; + for (y = 0, bufptr = buffer; y < 256; y ++) + { + for (x = 0; x < 256; x ++) + { + *bufptr++ = (unsigned char)y; + *bufptr++ = (unsigned char)(y + x); + *bufptr++ = (unsigned char)(y - x); + + if (i > 2) + { + // Add alpha channel + *bufptr++ = (unsigned char)((x + y) / 2); + } + } + } + break; + case 2 : // CMYK + case 5 : // CMYK + alpha + num_colors = 4; + for (y = 0, bufptr = buffer; y < 256; y ++) + { + for (x = 0; x < 256; x ++) + { + unsigned char cc = (unsigned char)y; + unsigned char mm = (unsigned char)(y + x); + unsigned char yy = (unsigned char)(y - x); + unsigned char kk = cc < mm ? cc < yy ? cc : yy : mm < yy ? mm : yy; + + *bufptr++ = (unsigned char)(cc - kk); + *bufptr++ = (unsigned char)(mm - kk); + *bufptr++ = (unsigned char)(yy - kk); + *bufptr++ = (unsigned char)(kk); + + if (i > 2) + { + // Add alpha channel + *bufptr++ = (unsigned char)((x + y) / 2); + } + } + } + break; + } + + // Write the image... + printf("pdfioFileCreateImageObjFromData(num_colors=%u, alpha=%s): ", (unsigned)num_colors, i > 2 ? "true" : "false"); + if ((images[i] = pdfioFileCreateImageObjFromData(pdf, buffer, 256, 256, num_colors, NULL, i > 2, false)) != NULL) + { + puts("PASS"); + } + else + { + puts("FAIL"); + return (1); + } + } + + // Create the page dictionary, object, and stream... + fputs("pdfioDictCreate: ", stdout); + if ((dict = pdfioDictCreate(pdf)) != NULL) + puts("PASS"); + else + return (1); + + for (i = 0; i < 6; i ++) + { + printf("pdfioPageDictAddImage(%d): ", i + 1); + snprintf(iname, sizeof(iname), "IM%d", i + 1); + if (pdfioPageDictAddImage(dict, pdfioStringCreate(pdf, iname), images[i])) + puts("PASS"); + else + return (1); + } + + fputs("pdfioPageDictAddFont(F1): ", stdout); + if (pdfioPageDictAddFont(dict, "F1", font)) + puts("PASS"); + else + return (1); + + printf("pdfioFileCreatePage(%d): ", number); + + if ((st = pdfioFileCreatePage(pdf, dict)) != NULL) + puts("PASS"); + else + return (1); + + if (write_header_footer(st, "Image Writing Test", number)) + goto error; + + // Draw images + for (i = 0; i < 6; i ++) + { + static const char *labels[] = { + "DeviceGray", + "DeviceRGB", + "DeviceCMYK", + "DeviceGray + Alpha", + "DeviceRGB + Alpha", + "DeviceCMYK + Alpha" + }; + + snprintf(iname, sizeof(iname), "IM%d", i + 1); + + if (draw_image(st, iname, 36 + 180 * (i % 3), 306 - 216 * (i / 3), 144, 144, labels[i])) + goto error; + } + + // Wrap up... + fputs("pdfioStreamClose: ", stdout); + if (pdfioStreamClose(st)) + puts("PASS"); + else + return (1); + + return (0); + + error: + + pdfioStreamClose(st); + return (1); +} + + // // 'write_color_patch()' - Write a color patch... // @@ -1594,9 +1775,10 @@ write_image_object( // static int // O - 1 on failure, 0 on success -write_images_test(pdfio_file_t *pdf, // I - PDF file - int number, // I - Page number - pdfio_obj_t *font) // I - Text font +write_images_test( + pdfio_file_t *pdf, // I - PDF file + int number, // I - Page number + pdfio_obj_t *font) // I - Text font { pdfio_dict_t *dict; // Page dictionary pdfio_stream_t *st; // Page stream