2025-08-08 20:00:36 +02:00

726 lines
19 KiB
C

/**************************************************************************/
/* */
/* Copyright (c) 2001, 2011 NoMachine (http://www.nomachine.com) */
/* Copyright (c) 2008-2017 Oleksandr Shneyder <o.shneyder@phoca-gmbh.de> */
/* Copyright (c) 2014-2022 Ulrich Sibiller <uli42@gmx.de> */
/* Copyright (c) 2014-2019 Mihai Moldovan <ionic@ionic.de> */
/* Copyright (c) 2011-2022 Mike Gabriel <mike.gabriel@das-netzwerkteam.de>*/
/* Copyright (c) 2015-2016 Qindel Group (http://www.qindel.com) */
/* */
/* NXCOMPEXT, NX protocol compression and NX extensions to this software */
/* are copyright of the aforementioned persons and companies. */
/* */
/* Redistribution and use of the present software is allowed according */
/* to terms specified in the file LICENSE which comes in the source */
/* distribution. */
/* */
/* All rights reserved. */
/* */
/* NOTE: This software has received contributions from various other */
/* contributors, only the core maintainers and supporters are listed as */
/* copyright holders. Please contact us, if you feel you should be listed */
/* as copyright holder, as well. */
/* */
/**************************************************************************/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <nx-X11/Xutil.h>
#include "Compext.h"
#include "Mask.h"
#include "Png.h"
#include "../Utils.h"
#define PANIC
#define WARNING
#undef TEST
#undef DEBUG
/*
* Selected ZLIB compression level.
*/
#define PNG_Z_LEVEL 4
/*
* Local function prototypes.
*/
static void PrepareRowForPng(CARD8 *dst, int y, int count);
static void PrepareRowForPng24(CARD8 *dst, int y, int count);
static void PrepareRowForPng16(CARD8 *dst, int y, int count);
static void PrepareRowForPng32(CARD8 *dst, int y, int count);
static void PngWriteData(png_structp png_ptr, png_bytep data, png_size_t length);
static void PngFlushData(png_structp png_ptr);
/*
* Image characteristics.
*/
static int bytesPerLine;
static int byteOrder;
static CARD8 bitsPerPixel;
static CARD16 redMax, greenMax, blueMax;
static CARD8 redShift, greenShift, blueShift;
/*
* Other variables used for the Png
* encoding.
*/
png_byte color_type;
png_structp png_ptr;
png_infop info_ptr;
png_colorp palette;
static char *pngCompBuf;
static int pngDataLen;
static char *pngBeforeBuf = NULL;
/*
* Allocate data for the compressed image.
* We need to ensure that there is enough
* space to include the palette and the
* header.
*/
#define PNG_DEST_SIZE(width, height) ((width) * 3 * (height) + 1024 + 256)
/*
* Just for debug purposes.
*/
#ifdef DEBUG
static int pngId;
static char pngName[10];
static FILE *pngFile;
#endif
int PngCompareColorTable(NXColorTable *c1, NXColorTable *c2)
{
return (c1 -> pixel - c2 -> pixel);
}
#define NB_COLOR_MAX 256
int NXCreatePalette32(XImage *src_image, NXColorTable *color_table, CARD8 *image_index, int nb_max)
{
int x, y, t, p;
CARD8 *fbptr;
CARD32 pixel;
fbptr = (CARD8 *) (src_image -> data);
/*
* TODO: Find a more intelligent way to
* estimate the number of colors.
*/
memset(color_table, 0, nb_max * sizeof(NXColorTable));
for (x = 0, p = 0; x < src_image -> height; x++)
{
for (y = 0; y < src_image -> width; y++)
{
if (byteOrder == LSBFirst)
{
pixel = (CARD32) *(fbptr + 3);
pixel = (pixel << 8) | (CARD32) *(fbptr + 2);
pixel = (pixel << 8) | (CARD32) *(fbptr + 1);
pixel = (pixel << 8) | (CARD32) *fbptr;
}
else
{
pixel = (CARD32) *fbptr;
pixel = (pixel << 8) | (CARD32) *(fbptr + 1);
pixel = (pixel << 8) | (CARD32) *(fbptr + 2);
pixel = (pixel << 8) | (CARD32) *(fbptr + 3);
}
fbptr += 4;
for (t = 0; t < nb_max; t++)
{
if (color_table[t].found == 0)
{
color_table[t].pixel = pixel;
color_table[t].found = 1;
p++;
image_index[((x * src_image -> width) + y)] = t;
break;
}
else if ((CARD32)(color_table[t].pixel) == pixel)
{
image_index[((x * src_image -> width) + y)] = t;
break;
}
}
if (p == nb_max)
{
return nb_max + 1;
}
}
}
return p;
}
int NXCreatePalette16(XImage *src_image, NXColorTable *color_table, CARD8 *image_index, int nb_max)
{
int x, y, t, p;
CARD8 *fbptr;
CARD16 pixel;
fbptr = (CARD8 *) (src_image -> data);
/*
* TODO: Find a more intelligent way to
* estimate the number of colors.
*/
memset(color_table, 0, nb_max * sizeof(NXColorTable));
for (x = 0, p = 0; x < src_image -> height; x++)
{
for (y = 0; y < src_image -> width; y++)
{
if (byteOrder == LSBFirst)
{
pixel = (CARD16) *(fbptr + 1);
pixel = (pixel << 8) | (CARD16) *fbptr;
}
else
{
pixel = (CARD16) *fbptr;
pixel = (pixel << 8) | (CARD16) *(fbptr + 1);
}
fbptr += 2;
for (t = 0; t < nb_max; t++)
{
if (color_table[t].found == 0)
{
color_table[t].pixel = pixel;
color_table[t].found = 1;
p++;
image_index[((x * src_image -> width) + y)] = t;
break;
}
else if ((color_table[t].pixel) == pixel)
{
image_index[((x * src_image -> width) + y)] = t;
break;
}
}
/*
* In case the number of 16bit words is not even
* we have 2 padding bytes that we have to skip.
*/
if ((y == src_image -> width - 1) && (src_image -> width % 2 == 1)) fbptr += 2;
if (p == nb_max)
{
return nb_max + 1;
}
}
}
return p;
}
char *PngCompressData(XImage *image, int *compressed_size)
{
unsigned int num = 0;
CARD8 *srcBuf;
int dy, w, h;
int nb_colors;
NXColorTable color_table[NB_COLOR_MAX];
CARD8 *image_index;
*compressed_size = 0;
pngDataLen = 0;
/*
* Initialize the image stuff.
*/
bitsPerPixel = image -> bits_per_pixel;
bytesPerLine = image -> bytes_per_line;
byteOrder = image -> byte_order;
if (bitsPerPixel < 15)
{
#ifdef PANIC
fprintf(stderr, "******PngCompressData: PANIC! Can't compress images with [%d] bits per pixel.\n",
bitsPerPixel);
#endif
return NULL;
}
redShift = FindLSB(image -> red_mask) - 1;
greenShift = FindLSB(image -> green_mask) - 1;
blueShift = FindLSB(image -> blue_mask) - 1;
redMax = image -> red_mask >> redShift;
greenMax = image -> green_mask >> greenShift;
blueMax = image -> blue_mask >> blueShift;
w = image -> width;
h = image -> height;
pngBeforeBuf = image -> data;
#ifdef DEBUG
fprintf(stderr, "******PngCompressData: Compressing image with width [%d] height [%d].\n",
w, h );
#endif
/*
* Initialize the PNG stuff.
*/
png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
if (png_ptr == NULL)
{
#ifdef PANIC
fprintf(stderr, "******PngCompressData: PANIC! Failed creating the png_create_write_struct.\n");
#endif
return NULL;
}
info_ptr = png_create_info_struct(png_ptr);
if (info_ptr == NULL)
{
#ifdef PANIC
fprintf(stderr, "******PngCompressData: PANIC! Failed creating the png_create_info_struct.\n");
#endif
png_destroy_write_struct(&png_ptr, NULL);
return NULL;
}
if (setjmp(png_jmpbuf(png_ptr)))
{
#ifdef PANIC
fprintf(stderr, "******PngCompressData: PANIC! Error during compression initialization.\n");
#endif
png_destroy_write_struct(&png_ptr, &info_ptr);
return NULL;
}
/*
* Be sure we allocate enough data.
*/
#ifdef TEST
fprintf(stderr, "******PngCompressData: Allocating [%d] bytes for the destination data.\n",
PNG_DEST_SIZE(w, h));
#endif
pngCompBuf = malloc(PNG_DEST_SIZE(w, h));
if (pngCompBuf == NULL)
{
#ifdef PANIC
fprintf(stderr, "******PngCompressData: PANIC! Error allocating [%d] bytes for the Png data.\n",
PNG_DEST_SIZE(w, h));
#endif
return NULL;
}
png_set_write_fn(png_ptr, (void *) pngCompBuf, PngWriteData, PngFlushData);
if (setjmp(png_jmpbuf(png_ptr)))
{
#ifdef PANIC
fprintf(stderr, "******PngCompressData: PANIC! Error writing the header.\n");
#endif
png_destroy_write_struct(&png_ptr, &info_ptr);
SAFE_free(pngCompBuf);
return NULL;
}
image_index = (CARD8 *) calloc(1, (image -> height) * (image -> width) * sizeof(CARD8));
if (image_index == NULL)
{
#ifdef PANIC
fprintf(stderr, "******PngCompressData: PANIC! Could not alloc image_index.\n");
#endif
SAFE_free(pngCompBuf);
return NULL;
}
/*
* TODO: Be sure the padded bytes are cleaned.
* It would be better to set to zero the bytes
* that are not aligned to the word boundary
* at the end of the procedure.
*/
png_set_compression_level(png_ptr, PNG_Z_LEVEL);
if (bitsPerPixel == 16)
{
nb_colors = NXCreatePalette16(image, color_table, image_index, NB_COLOR_MAX);
}
else
{
nb_colors = NXCreatePalette32(image, color_table, image_index, NB_COLOR_MAX);
}
if (nb_colors <= NB_COLOR_MAX)
{
color_type = PNG_COLOR_TYPE_PALETTE;
}
else
{
color_type = PNG_COLOR_TYPE_RGB;
}
png_set_IHDR(png_ptr, info_ptr, w, h,
8, color_type, PNG_INTERLACE_NONE,
PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE);
if (color_type == PNG_COLOR_TYPE_PALETTE)
{
palette = png_malloc(png_ptr, sizeof(*palette) * 256);
/*
* TODO: Do we need to clean these bytes?
*
* memset(palette, 0, sizeof(*palette) * 256);
*/
for (num = 0; num < 256 && color_table[num].found != 0; num++)
{
if (bitsPerPixel == 24)
{
palette[num].red = (color_table[num].pixel >> redShift) & redMax;
palette[num].green = (color_table[num].pixel >> greenShift) & greenMax;
palette[num].blue = color_table[num].pixel >> blueShift & blueMax;
}
else
{
int inRed, inGreen, inBlue;
inRed = (color_table[num].pixel >> redShift) & redMax;
inGreen = (color_table[num].pixel >> greenShift) & greenMax;
inBlue = color_table[num].pixel >> blueShift & blueMax;
palette[num].red = (CARD8)((inRed * 255 + redMax / 2) / redMax);
palette[num].green = (CARD8)((inGreen * 255 + greenMax / 2) / greenMax);
palette[num].blue = (CARD8)((inBlue * 255 + blueMax / 2) / blueMax);
}
#ifdef DEBUG
fprintf(stderr, "******PngCompressData: pixel[%d] r[%d] g[%d] b[%d].\n",
(int) color_table[num].pixel,palette[num].red,palette[num].green,palette[num].blue);
#endif
}
png_set_PLTE(png_ptr, info_ptr, palette, num);
#ifdef DEBUG
fprintf(stderr, "******PngCompressedData: Setting palette.\n");
#endif
}
/*
* End of palette.
*/
png_write_info(png_ptr, info_ptr);
/*
* Allocate space for one line of
* the image, 3 bytes per pixel.
*/
#ifdef DEBUG
fprintf(stderr, "******PngCompressedData: Initialization finished.\n");
#endif
if (setjmp(png_jmpbuf(png_ptr)))
{
#ifdef PANIC
fprintf(stderr, "******PngCompressData: PANIC! Error while writing the image rows.\n");
#endif
png_destroy_write_struct(&png_ptr, &info_ptr);
SAFE_free(pngCompBuf);
SAFE_free(image_index);
return NULL;
}
int count;
if (color_type == PNG_COLOR_TYPE_PALETTE)
{
count = w;
}
else
{
count = 3 * w;
}
srcBuf = (CARD8 *) calloc(count, sizeof(CARD8));
if (srcBuf == NULL)
{
#ifdef PANIC
fprintf(stderr, "******PngCompressData: PANIC! Cannot allocate [%d] bytes.\n",
(int) (count * sizeof(CARD8)));
#endif
SAFE_free(pngCompBuf);
SAFE_free(image_index);
return NULL;
}
/*
* TODO: Be sure the padded bytes are cleaned.
* It would be better to set to zero the bytes
* that are not aligned to the word boundary
* at the end of the procedure.
*/
for (dy = 0; dy < h; dy++)
{
if (color_type == PNG_COLOR_TYPE_RGB)
{
PrepareRowForPng(srcBuf, dy, w);
}
else
{
memcpy(srcBuf, image_index + (dy * w), w);
}
png_write_row(png_ptr, srcBuf);
}
#ifdef DEBUG
fprintf(stderr, "******PngCompressedData: Compression finished. Lines handled [%d,%d].\n",
dy, h);
#endif
SAFE_free(srcBuf);
SAFE_free(image_index);
if (setjmp(png_jmpbuf(png_ptr)))
{
#ifdef PANIC
fprintf(stderr, "******PngCompressData: PANIC! error during end of write.\n");
#endif
png_destroy_write_struct(&png_ptr, &info_ptr);
SAFE_free(pngCompBuf);
return NULL;
}
png_write_end(png_ptr, NULL);
if (color_type == PNG_COLOR_TYPE_PALETTE)
{
png_free(png_ptr, palette);
}
png_destroy_write_struct(&png_ptr, &info_ptr);
/*
* Check the size of the resulting data.
*/
if (pngDataLen > 0)
{
#ifdef DEBUG
int i = 0;
fprintf(stderr, "******PngCompressedData: Compressed size [%d].\n",
pngDataLen);
pngId++;
sprintf(pngName, "png%d", pngId);
pngFile = fopen(pngName, "w");
for (i = 0; i < pngDataLen; i++)
{
fprintf(pngFile, "%c", *(pngCompBuf + i));
}
fclose(pngFile);
#endif
*compressed_size = pngDataLen;
return pngCompBuf;
}
else
{
#ifdef DEBUG
fprintf(stderr, "******PngCompressedData: PANIC! Invalid size of the compressed data [%d].\n",
pngDataLen);
#endif
SAFE_free(pngCompBuf);
return NULL;
}
}
static void PngWriteData(png_structp _png_ptr, png_bytep data, png_size_t length)
{
memcpy(((char *) png_get_io_ptr(_png_ptr) + pngDataLen), data, length);
pngDataLen += length;
}
static void PngFlushData(png_structp _png_ptr)
{
}
void PrepareRowForPng(CARD8 *dst, int y, int count)
{
if (bitsPerPixel == 32)
{
if (redMax == 0xff &&
greenMax == 0xff &&
blueMax == 0xff)
{
PrepareRowForPng24(dst, y, count);
}
else
{
PrepareRowForPng32(dst, y, count);
}
}
else if (bitsPerPixel == 24)
{
memcpy(dst, pngBeforeBuf + y * bytesPerLine, count * 3);
}
else
{
/*
* 16 bpp assumed.
*/
PrepareRowForPng16(dst, y, count);
}
}
void PrepareRowForPng24(CARD8 *dst, int y, int count)
{
CARD8 *fbptr;
CARD32 pix;
fbptr = (CARD8 *) (pngBeforeBuf + y * bytesPerLine);
while (count--)
{
if (byteOrder == LSBFirst)
{
pix = (CARD32) *(fbptr + 2);
pix = (pix << 8) | (CARD32) *(fbptr+1);
pix = (pix << 8) | (CARD32) *fbptr;
}
else
{
pix = (CARD32) *(fbptr + 1);
pix = (pix << 8) | (CARD32) *(fbptr + 2);
pix = (pix << 8) | (CARD32) *(fbptr + 3);
}
*dst++ = (CARD8)(pix >> redShift);
*dst++ = (CARD8)(pix >> greenShift);
*dst++ = (CARD8)(pix >> blueShift);
fbptr+=4;
}
}
#define DEFINE_PNG_GET_ROW_FUNCTION(bpp) \
\
void PrepareRowForPng##bpp(CARD8 *dst, int y, int count) \
{ \
CARD8 *fbptr; \
CARD##bpp pix; \
int inRed, inGreen, inBlue; \
int i; \
\
fbptr = (CARD8 *) (pngBeforeBuf + y * bytesPerLine); \
\
while (count--) \
{ \
pix = 0; \
\
if (byteOrder == LSBFirst) \
{ \
for (i = (bpp >> 3) - 1; i >= 0; i--) \
{ \
pix = (pix << 8) | (CARD32) *(fbptr + i); \
} \
} \
else \
{ \
for (i = 0; i < (bpp >> 3); i++) \
{ \
pix = (pix << 8) | (CARD32) *(fbptr + i); \
} \
} \
\
fbptr += (bpp >> 3); \
\
inRed = (int) \
(pix >> redShift & redMax); \
inGreen = (int) \
(pix >> greenShift & greenMax); \
inBlue = (int) \
(pix >> blueShift & blueMax); \
*dst++ = (CARD8)((inRed * 255 + redMax / 2) / \
redMax); \
*dst++ = (CARD8)((inGreen * 255 + greenMax / 2) / \
greenMax); \
*dst++ = (CARD8)((inBlue * 255 + blueMax / 2) / \
blueMax); \
} \
}
DEFINE_PNG_GET_ROW_FUNCTION(16)
DEFINE_PNG_GET_ROW_FUNCTION(32)