/* pngwtran.c - transforms the data in a row for PNG writers * * Last changed in libpng 1.6.17 [(PENDING RELEASE)] * Copyright (c) 1998-2015 Glenn Randers-Pehrson * (Version 0.96 Copyright (c) 1996, 1997 Andreas Dilger) * (Version 0.88 Copyright (c) 1995, 1996 Guy Eric Schalnat, Group 42, Inc.) * * This code is released under the libpng license. * For conditions of distribution and use, see the disclaimer * and license in png.h */ #include "pngpriv.h" #define PNG_SRC_FILE PNG_SRC_FILE_pngwtran #ifdef PNG_WRITE_SUPPORTED #ifdef PNG_WRITE_TRANSFORMS_SUPPORTED #ifdef PNG_WRITE_PACK_SUPPORTED /* Pack pixels into bytes. Get the true bit depth from png_ptr. The * row_info bit depth should be 8 (one pixel per byte). The channels * should be 1 (this only happens on grayscale and paletted images). */ static void png_do_pack(png_transform_controlp row_info, png_bytep row) { png_debug(1, "in png_do_pack"); # define png_ptr row_info->png_ptr /* The comment suggests the following must be true. * TODO: test this. */ affirm(row_info->bit_depth == 8 && row_info->channels == 1); { switch (png_ptr->bit_depth) { case 1: { png_const_bytep ep = row + png_transform_rowbytes(row_info); png_bytep dp = row; unsigned int mask = 0x80, v = 0; while (row < ep) { if (*row++ != 0) v |= mask; mask >>= 1; if (mask == 0) { mask = 0x80; *dp++ = (png_byte)/*SAFE*/v; v = 0; } } if (mask != 0x80) *dp++ = (png_byte)/*SAFE*/v; row_info->bit_depth = 1; break; } case 2: { png_const_bytep ep = row + png_transform_rowbytes(row_info); png_bytep dp = row; unsigned int shift = 8, v = 0; while (row < ep) { shift -= 2; v |= (*row++ & 0x3) << shift; if (shift == 0) { shift = 8; *dp++ = png_check_byte(png_ptr, v); v = 0; } } if (shift != 8) *dp++ = png_check_byte(png_ptr, v); row_info->bit_depth = 2; break; } case 4: { png_const_bytep ep = row + png_transform_rowbytes(row_info); png_bytep dp = row; unsigned int shift = 8, v = 0; while (row < ep) { shift -= 4; v |= ((*row++ & 0xf) << shift); if (shift == 0) { shift = 8; *dp++ = png_check_byte(png_ptr, v); v = 0; } } if (shift != 8) *dp++ = png_check_byte(png_ptr, v); row_info->bit_depth = 4; break; } default: break; } } # undef png_ptr } #endif #ifdef PNG_WRITE_SHIFT_SUPPORTED /* Shift pixel values to take advantage of whole range. Pass the * true number of bits in bit_depth. The row should be packed * according to row_info->bit_depth. Thus, if you had a row of * bit depth 4, but the pixels only had values from 0 to 7, you * would pass 3 as bit_depth, and this routine would translate the * data to 0 to 15. * * NOTE: this is horrible complexity for no value. Once people suggested they * were selling 16-bit displays with 5:6:5 bits spread R:G:B but so far as I * could determine these displays produced intermediate grey (uncolored) colors, * which is impossible with a true 5:6:5, so most likely 5:6:5 was marketing. */ static void png_do_shift(png_transform_controlp row_info, png_bytep row) { png_debug(1, "in png_do_shift"); # define png_ptr row_info->png_ptr if (!(row_info->flags & PNG_INDEXED) && (row_info->channels-1) <= 3) { png_const_color_8p bit_depth = &png_ptr->shift; int shift_start[4], shift_dec[4]; int channels = 0; if (row_info->channels == 3 || row_info->channels == 4) { shift_start[channels] = row_info->bit_depth - bit_depth->red; shift_dec[channels] = bit_depth->red; channels++; shift_start[channels] = row_info->bit_depth - bit_depth->green; shift_dec[channels] = bit_depth->green; channels++; shift_start[channels] = row_info->bit_depth - bit_depth->blue; shift_dec[channels] = bit_depth->blue; channels++; } else /* 1 or 2 channels */ { shift_start[channels] = row_info->bit_depth - bit_depth->gray; shift_dec[channels] = bit_depth->gray; channels++; } if (row_info->channels == 2 || row_info->channels == 4) { shift_start[channels] = row_info->bit_depth - bit_depth->alpha; shift_dec[channels] = bit_depth->alpha; channels++; } /* With low res depths, could only be grayscale, so one channel */ if (row_info->bit_depth < 8) { png_bytep bp = row; png_size_t i; unsigned int mask; size_t row_bytes = png_transform_rowbytes(row_info); affirm(row_info->channels == 1); if (bit_depth->gray == 1 && row_info->bit_depth == 2) mask = 0x55; else if (row_info->bit_depth == 4 && bit_depth->gray == 3) mask = 0x11; else mask = 0xff; for (i = 0; i < row_bytes; i++, bp++) { int j; unsigned int v, out; v = *bp; out = 0; for (j = shift_start[0]; j > -shift_dec[0]; j -= shift_dec[0]) { if (j > 0) out |= v << j; else out |= (v >> (-j)) & mask; } *bp = png_check_byte(png_ptr, out); } } else if (row_info->bit_depth == 8) { png_bytep bp = row; png_uint_32 i; png_uint_32 istop = channels * row_info->width; for (i = 0; i < istop; i++, bp++) { const unsigned int c = i%channels; int j; unsigned int v, out; v = *bp; out = 0; for (j = shift_start[c]; j > -shift_dec[c]; j -= shift_dec[c]) { if (j > 0) out |= v << j; else out |= v >> (-j); } *bp = png_check_byte(png_ptr, out); } } else { png_bytep bp; png_uint_32 i; png_uint_32 istop = channels * row_info->width; for (bp = row, i = 0; i < istop; i++) { const unsigned int c = i%channels; int j; unsigned int value, v; v = png_get_uint_16(bp); value = 0; for (j = shift_start[c]; j > -shift_dec[c]; j -= shift_dec[c]) { if (j > 0) value |= v << j; else value |= v >> (-j); } *bp++ = png_check_byte(png_ptr, value >> 8); *bp++ = PNG_BYTE(value); } } } # undef png_ptr } #endif #ifdef PNG_WRITE_SWAP_ALPHA_SUPPORTED static void png_do_write_swap_alpha(png_transform_controlp row_info, png_bytep row) { png_debug(1, "in png_do_write_swap_alpha"); # define png_ptr row_info->png_ptr { if (row_info->channels == 4) { if (row_info->bit_depth == 8) { png_const_bytep ep = row + png_transform_rowbytes(row_info) - 4; /* This converts from ARGB to RGBA */ while (row <= ep) { png_byte save = row[0]; row[0] = row[1]; row[1] = row[2]; row[2] = row[3]; row[3] = save; row += 4; } debug(row == ep+4); } #ifdef PNG_WRITE_16BIT_SUPPORTED else if (row_info->bit_depth == 16) { /* This converts from AARRGGBB to RRGGBBAA */ png_const_bytep ep = row + png_transform_rowbytes(row_info) - 8; while (row <= ep) { png_byte s0 = row[0]; png_byte s1 = row[1]; memmove(row, row+2, 6); row[6] = s0; row[7] = s1; row += 8; } debug(row == ep+8); } #endif /* WRITE_16BIT */ } else if (row_info->channels == 2) { if (row_info->bit_depth == 8) { /* This converts from AG to GA */ png_const_bytep ep = row + png_transform_rowbytes(row_info) - 2; /* This converts from ARGB to RGBA */ while (row <= ep) { png_byte save = *row; *row = row[1], ++row; *row++ = save; } debug(row == ep+2); } #ifdef PNG_WRITE_16BIT_SUPPORTED else { /* This converts from AAGG to GGAA */ png_const_bytep ep = row + png_transform_rowbytes(row_info) - 4; while (row <= ep) { png_byte save = row[0]; row[0] = row[2]; row[2] = save; save = row[1]; row[1] = row[3]; row[3] = save; row += 4; } debug(row == ep+4); } #endif /* WRITE_16BIT */ } } # undef png_ptr } #endif #ifdef PNG_WRITE_INVERT_ALPHA_SUPPORTED static void png_do_write_invert_alpha(png_transform_controlp row_info, png_bytep row) { png_debug(1, "in png_do_write_invert_alpha"); # define png_ptr row_info->png_ptr { if (row_info->channels == 4) { if (row_info->bit_depth == 8) { /* This inverts the alpha channel in RGBA */ png_const_bytep ep = row + png_transform_rowbytes(row_info) - 1; row += 3; /* alpha channel */ while (row <= ep) *row ^= 0xff, row += 4; } #ifdef PNG_WRITE_16BIT_SUPPORTED else if (row_info->bit_depth == 16) { /* This inverts the alpha channel in RRGGBBAA */ png_const_bytep ep = row + png_transform_rowbytes(row_info) - 2; row += 6; while (row <= ep) row[0] ^= 0xff, row[1] ^= 0xff, row += 8; } #endif /* WRITE_16BIT */ } else if (row_info->channels == 2) { if (row_info->bit_depth == 8) { /* This inverts the alpha channel in GA */ png_const_bytep ep = row + png_transform_rowbytes(row_info) - 1; ++row; while (row <= ep) *row ^= 0xff, row += 2; } #ifdef PNG_WRITE_16BIT_SUPPORTED else { /* This inverts the alpha channel in GGAA */ png_const_bytep ep = row + png_transform_rowbytes(row_info) - 2; row += 2; while (row <= ep) row[0] ^= 0xff, row[1] ^= 0xff, row += 4; } #endif /* WRITE_16BIT */ } } # undef png_ptr } #endif /* Transform the data according to the user's wishes. The order of * transformations is significant. */ void /* PRIVATE */ png_do_write_transformations(png_structrp png_ptr, png_row_infop row_info_in) { png_transform_control display; png_debug(1, "in png_do_write_transformations"); if (png_ptr == NULL) return; #ifdef PNG_WRITE_USER_TRANSFORM_SUPPORTED if ((png_ptr->transformations & PNG_USER_TRANSFORM) != 0) if (png_ptr->write_user_transform_fn != NULL) (*(png_ptr->write_user_transform_fn)) /* User write transform function */ (png_ptr, /* png_ptr */ row_info_in, /* row_info: */ /* png_uint_32 width; width of row */ /* png_size_t rowbytes; number of bytes in row */ /* png_byte color_type; color type of pixels */ /* png_byte bit_depth; bit depth of samples */ /* png_byte channels; number of channels (1-4) */ /* png_byte pixel_depth; bits per pixel (depth*channels) */ png_ptr->row_buf + 1); /* start of pixel data for row */ #endif png_init_transform_control(png_ptr, &display, row_info_in); #ifdef PNG_WRITE_FILLER_SUPPORTED if ((png_ptr->transformations & PNG_FILLER) != 0) png_do_strip_channel(&display, png_ptr->row_buf + 1, !(png_ptr->flags & PNG_FLAG_FILLER_AFTER)); #endif #ifdef PNG_WRITE_PACKSWAP_SUPPORTED if ((png_ptr->transformations & PNG_PACKSWAP) != 0) png_do_packswap(&display, png_ptr->row_buf + 1); #endif #ifdef PNG_WRITE_PACK_SUPPORTED if ((png_ptr->transformations & PNG_PACK) != 0) png_do_pack(&display, png_ptr->row_buf + 1); #endif #ifdef PNG_WRITE_SWAP_SUPPORTED # ifdef PNG_16BIT_SUPPORTED if ((png_ptr->transformations & PNG_SWAP_BYTES) != 0) png_do_swap(&display, png_ptr->row_buf + 1); # endif #endif #ifdef PNG_WRITE_SHIFT_SUPPORTED if ((png_ptr->transformations & PNG_SHIFT) != 0) png_do_shift(&display, png_ptr->row_buf + 1); #endif #ifdef PNG_WRITE_SWAP_ALPHA_SUPPORTED if ((png_ptr->transformations & PNG_SWAP_ALPHA) != 0) png_do_write_swap_alpha(&display, png_ptr->row_buf + 1); #endif #ifdef PNG_WRITE_INVERT_ALPHA_SUPPORTED if ((png_ptr->transformations & PNG_INVERT_ALPHA) != 0) png_do_write_invert_alpha(&display, png_ptr->row_buf + 1); #endif #ifdef PNG_WRITE_BGR_SUPPORTED if ((png_ptr->transformations & PNG_BGR) != 0) png_do_bgr(&display, png_ptr->row_buf + 1); #endif #ifdef PNG_WRITE_INVERT_SUPPORTED if ((png_ptr->transformations & PNG_INVERT_MONO) != 0) png_do_invert(&display, png_ptr->row_buf + 1); #endif /* Clear the flags; they are irrelevant because the write code is * reversing transformations to get PNG data but the shared transformation * code assumes input PNG data. Only PNG_INDEXED is required. */ if ((display.flags & PNG_BAD_INDEX) != 0) png_error(png_ptr, "palette data has out of range index"); display.flags &= PNG_INDEXED; png_end_transform_control(row_info_in, &display); } #endif /* WRITE_TRANSFORMS */ #endif /* WRITE */