diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-04 01:24:41 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-04 01:24:41 +0000 |
commit | a9bcc81f821d7c66f623779fa5147e728eb3c388 (patch) | |
tree | 98676963bcdd537ae5908a067a8eb110b93486a6 /winpr/libwinpr/utils/image.c | |
parent | Initial commit. (diff) | |
download | freerdp3-a9bcc81f821d7c66f623779fa5147e728eb3c388.tar.xz freerdp3-a9bcc81f821d7c66f623779fa5147e728eb3c388.zip |
Adding upstream version 3.3.0+dfsg1.upstream/3.3.0+dfsg1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'winpr/libwinpr/utils/image.c')
-rw-r--r-- | winpr/libwinpr/utils/image.c | 1258 |
1 files changed, 1258 insertions, 0 deletions
diff --git a/winpr/libwinpr/utils/image.c b/winpr/libwinpr/utils/image.c new file mode 100644 index 0000000..cc2e2ad --- /dev/null +++ b/winpr/libwinpr/utils/image.c @@ -0,0 +1,1258 @@ +/** + * WinPR: Windows Portable Runtime + * Image Utils + * + * Copyright 2014 Marc-Andre Moreau <marcandre.moreau@gmail.com> + * Copyright 2016 Inuvika Inc. + * Copyright 2016 David PHAM-VAN <d.phamvan@inuvika.com> + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <stdlib.h> + +#include <winpr/config.h> + +#include <winpr/wtypes.h> +#include <winpr/crt.h> +#include <winpr/file.h> + +#include <winpr/image.h> + +#if defined(WINPR_UTILS_IMAGE_PNG) +#include <png.h> +#endif + +#if defined(WINPR_UTILS_IMAGE_JPEG) +#define INT32 INT32_WINPR +#include <jpeglib.h> +#undef INT32 +#endif + +#if defined(WINPR_UTILS_IMAGE_WEBP) +#include <webp/encode.h> +#include <webp/decode.h> +#endif + +#if defined(WITH_LODEPNG) +#include <lodepng.h> +#endif +#include <winpr/stream.h> + +#include "../log.h" +#define TAG WINPR_TAG("utils.image") + +static SSIZE_T winpr_convert_from_jpeg(const char* comp_data, size_t comp_data_bytes, UINT32* width, + UINT32* height, UINT32* bpp, char** ppdecomp_data); +static SSIZE_T winpr_convert_from_png(const char* comp_data, size_t comp_data_bytes, UINT32* width, + UINT32* height, UINT32* bpp, char** ppdecomp_data); +static SSIZE_T winpr_convert_from_webp(const char* comp_data, size_t comp_data_bytes, UINT32* width, + UINT32* height, UINT32* bpp, char** ppdecomp_data); + +static BOOL writeBitmapFileHeader(wStream* s, const WINPR_BITMAP_FILE_HEADER* bf) +{ + if (!Stream_EnsureRemainingCapacity(s, sizeof(WINPR_BITMAP_FILE_HEADER))) + return FALSE; + + Stream_Write_UINT8(s, bf->bfType[0]); + Stream_Write_UINT8(s, bf->bfType[1]); + Stream_Write_UINT32(s, bf->bfSize); + Stream_Write_UINT16(s, bf->bfReserved1); + Stream_Write_UINT16(s, bf->bfReserved2); + Stream_Write_UINT32(s, bf->bfOffBits); + return TRUE; +} + +static BOOL readBitmapFileHeader(wStream* s, WINPR_BITMAP_FILE_HEADER* bf) +{ + if (!s || !bf || (!Stream_CheckAndLogRequiredLength(TAG, s, sizeof(WINPR_BITMAP_FILE_HEADER)))) + return FALSE; + + Stream_Read_UINT8(s, bf->bfType[0]); + Stream_Read_UINT8(s, bf->bfType[1]); + Stream_Read_UINT32(s, bf->bfSize); + Stream_Read_UINT16(s, bf->bfReserved1); + Stream_Read_UINT16(s, bf->bfReserved2); + Stream_Read_UINT32(s, bf->bfOffBits); + + if (bf->bfSize < sizeof(WINPR_BITMAP_FILE_HEADER)) + { + WLog_ERR(TAG, ""); + return FALSE; + } + + return Stream_CheckAndLogRequiredCapacity(TAG, s, + bf->bfSize - sizeof(WINPR_BITMAP_FILE_HEADER)); +} + +static BOOL writeBitmapInfoHeader(wStream* s, const WINPR_BITMAP_INFO_HEADER* bi) +{ + if (!Stream_EnsureRemainingCapacity(s, sizeof(WINPR_BITMAP_INFO_HEADER))) + return FALSE; + + Stream_Write_UINT32(s, bi->biSize); + Stream_Write_INT32(s, bi->biWidth); + Stream_Write_INT32(s, bi->biHeight); + Stream_Write_UINT16(s, bi->biPlanes); + Stream_Write_UINT16(s, bi->biBitCount); + Stream_Write_UINT32(s, bi->biCompression); + Stream_Write_UINT32(s, bi->biSizeImage); + Stream_Write_INT32(s, bi->biXPelsPerMeter); + Stream_Write_INT32(s, bi->biYPelsPerMeter); + Stream_Write_UINT32(s, bi->biClrUsed); + Stream_Write_UINT32(s, bi->biClrImportant); + return TRUE; +} + +static BOOL readBitmapInfoHeader(wStream* s, WINPR_BITMAP_INFO_HEADER* bi, size_t* poffset) +{ + if (!s || !bi || (!Stream_CheckAndLogRequiredLength(TAG, s, sizeof(WINPR_BITMAP_INFO_HEADER)))) + return FALSE; + + const size_t start = Stream_GetPosition(s); + Stream_Read_UINT32(s, bi->biSize); + Stream_Read_INT32(s, bi->biWidth); + Stream_Read_INT32(s, bi->biHeight); + Stream_Read_UINT16(s, bi->biPlanes); + Stream_Read_UINT16(s, bi->biBitCount); + Stream_Read_UINT32(s, bi->biCompression); + Stream_Read_UINT32(s, bi->biSizeImage); + Stream_Read_INT32(s, bi->biXPelsPerMeter); + Stream_Read_INT32(s, bi->biYPelsPerMeter); + Stream_Read_UINT32(s, bi->biClrUsed); + Stream_Read_UINT32(s, bi->biClrImportant); + + if ((bi->biBitCount < 1) || (bi->biBitCount > 32)) + { + WLog_WARN(TAG, "invalid biBitCount=%" PRIu32, bi->biBitCount); + return FALSE; + } + + /* https://learn.microsoft.com/en-us/windows/win32/api/wingdi/ns-wingdi-bitmapinfoheader */ + size_t offset = 0; + switch (bi->biCompression) + { + case BI_RGB: + if (bi->biBitCount <= 8) + { + DWORD used = bi->biClrUsed; + if (used == 0) + used = (1 << bi->biBitCount) / 8; + offset += sizeof(RGBQUAD) * used; + } + if (bi->biSizeImage == 0) + { + UINT32 stride = ((((bi->biWidth * bi->biBitCount) + 31) & ~31) >> 3); + bi->biSizeImage = abs(bi->biHeight) * stride; + } + break; + case BI_BITFIELDS: + offset += sizeof(DWORD) * 3; // 3 DWORD color masks + break; + default: + WLog_ERR(TAG, "unsupported biCompression %" PRIu32, bi->biCompression); + return FALSE; + } + + if (bi->biSizeImage == 0) + { + WLog_ERR(TAG, "invalid biSizeImage %" PRIuz, bi->biSizeImage); + return FALSE; + } + + const size_t pos = Stream_GetPosition(s) - start; + if (bi->biSize < pos) + { + WLog_ERR(TAG, "invalid biSize %" PRIuz " < (actual) offset %" PRIuz, bi->biSize, pos); + return FALSE; + } + + *poffset = offset; + return Stream_SafeSeek(s, bi->biSize - pos); +} + +BYTE* winpr_bitmap_construct_header(size_t width, size_t height, size_t bpp) +{ + BYTE* result = NULL; + WINPR_BITMAP_FILE_HEADER bf = { 0 }; + WINPR_BITMAP_INFO_HEADER bi = { 0 }; + wStream* s = NULL; + size_t imgSize = 0; + + imgSize = width * height * (bpp / 8); + if ((width > INT32_MAX) || (height > INT32_MAX) || (bpp > UINT16_MAX) || (imgSize > UINT32_MAX)) + return NULL; + + s = Stream_New(NULL, WINPR_IMAGE_BMP_HEADER_LEN); + if (!s) + return NULL; + + bf.bfType[0] = 'B'; + bf.bfType[1] = 'M'; + bf.bfReserved1 = 0; + bf.bfReserved2 = 0; + bi.biSize = (UINT32)sizeof(WINPR_BITMAP_INFO_HEADER); + bf.bfOffBits = (UINT32)sizeof(WINPR_BITMAP_FILE_HEADER) + bi.biSize; + bi.biSizeImage = (UINT32)imgSize; + bf.bfSize = bf.bfOffBits + bi.biSizeImage; + bi.biWidth = (INT32)width; + bi.biHeight = -1 * (INT32)height; + bi.biPlanes = 1; + bi.biBitCount = (UINT16)bpp; + bi.biCompression = BI_RGB; + bi.biXPelsPerMeter = (INT32)width; + bi.biYPelsPerMeter = (INT32)height; + bi.biClrUsed = 0; + bi.biClrImportant = 0; + + size_t offset = 0; + switch (bi.biCompression) + { + case BI_RGB: + if (bi.biBitCount <= 8) + { + DWORD used = bi.biClrUsed; + if (used == 0) + used = (1 << bi.biBitCount) / 8; + offset += sizeof(RGBQUAD) * used; + } + break; + case BI_BITFIELDS: + offset += sizeof(DWORD) * 3; // 3 DWORD color masks + break; + default: + return FALSE; + } + + if (!writeBitmapFileHeader(s, &bf)) + goto fail; + + if (!writeBitmapInfoHeader(s, &bi)) + goto fail; + + if (!Stream_EnsureRemainingCapacity(s, offset)) + goto fail; + + Stream_Zero(s, offset); + result = Stream_Buffer(s); +fail: + Stream_Free(s, result == 0); + return result; +} + +/** + * Refer to "Compressed Image File Formats: JPEG, PNG, GIF, XBM, BMP" book + */ + +static void* winpr_bitmap_write_buffer(const BYTE* data, size_t size, UINT32 width, UINT32 height, + UINT32 stride, UINT32 bpp, UINT32* pSize) +{ + WINPR_ASSERT(data || (size == 0)); + + void* result = NULL; + const size_t bpp_stride = 1ull * width * (bpp / 8); + wStream* s = Stream_New(NULL, 1024); + + if (stride == 0) + stride = bpp_stride; + + BYTE* bmp_header = winpr_bitmap_construct_header(width, height, bpp); + if (!bmp_header) + goto fail; + if (!Stream_EnsureRemainingCapacity(s, WINPR_IMAGE_BMP_HEADER_LEN)) + goto fail; + Stream_Write(s, bmp_header, WINPR_IMAGE_BMP_HEADER_LEN); + + if (!Stream_EnsureRemainingCapacity(s, stride * height * 1ull)) + goto fail; + + for (size_t y = 0; y < height; y++) + { + const BYTE* line = &data[stride * y]; + + Stream_Write(s, line, stride); + } + + result = Stream_Buffer(s); + *pSize = Stream_GetPosition(s); +fail: + Stream_Free(s, result == NULL); + free(bmp_header); + return result; +} + +int winpr_bitmap_write(const char* filename, const BYTE* data, size_t width, size_t height, + size_t bpp) +{ + return winpr_bitmap_write_ex(filename, data, 0, width, height, bpp); +} + +int winpr_bitmap_write_ex(const char* filename, const BYTE* data, size_t stride, size_t width, + size_t height, size_t bpp) +{ + FILE* fp = NULL; + int ret = -1; + const size_t bpp_stride = ((((width * bpp) + 31) & ~31) >> 3); + + if (stride == 0) + stride = bpp_stride; + + UINT32 bmpsize = 0; + const size_t size = stride * 1ull * height; + void* bmpdata = winpr_bitmap_write_buffer(data, size, width, height, stride, bpp, &bmpsize); + if (!bmpdata) + goto fail; + + fp = winpr_fopen(filename, "w+b"); + if (!fp) + { + WLog_ERR(TAG, "failed to open file %s", filename); + goto fail; + } + + if (fwrite(bmpdata, bmpsize, 1, fp) != 1) + goto fail; + +fail: + if (fp) + fclose(fp); + free(bmpdata); + return ret; +} + +static int write_and_free(const char* filename, void* data, size_t size) +{ + int status = -1; + if (!data) + goto fail; + + FILE* fp = winpr_fopen(filename, "w+b"); + if (!fp) + goto fail; + + size_t w = fwrite(data, 1, size, fp); + fclose(fp); + + status = (w == size) ? 1 : -1; +fail: + free(data); + return status; +} + +int winpr_image_write(wImage* image, const char* filename) +{ + WINPR_ASSERT(image); + return winpr_image_write_ex(image, image->type, filename); +} + +int winpr_image_write_ex(wImage* image, UINT32 format, const char* filename) +{ + WINPR_ASSERT(image); + + size_t size = 0; + void* data = winpr_image_write_buffer(image, format, &size); + if (!data) + return -1; + return write_and_free(filename, data, size); +} + +static int winpr_image_bitmap_read_buffer(wImage* image, const BYTE* buffer, size_t size) +{ + int rc = -1; + BOOL vFlip = 0; + WINPR_BITMAP_FILE_HEADER bf = { 0 }; + WINPR_BITMAP_INFO_HEADER bi = { 0 }; + wStream sbuffer = { 0 }; + wStream* s = Stream_StaticConstInit(&sbuffer, buffer, size); + + if (!s) + return -1; + + size_t bmpoffset = 0; + if (!readBitmapFileHeader(s, &bf) || !readBitmapInfoHeader(s, &bi, &bmpoffset)) + goto fail; + + if ((bf.bfType[0] != 'B') || (bf.bfType[1] != 'M')) + { + WLog_WARN(TAG, "Invalid bitmap header %c%c", bf.bfType[0], bf.bfType[1]); + goto fail; + } + + image->type = WINPR_IMAGE_BITMAP; + + const size_t pos = Stream_GetPosition(s); + const size_t expect = bf.bfOffBits; + + if (pos != expect) + { + WLog_WARN(TAG, "pos=%" PRIuz ", expected %" PRIuz ", offset=" PRIuz, pos, expect, + bmpoffset); + goto fail; + } + + if (!Stream_CheckAndLogRequiredCapacity(TAG, s, bi.biSizeImage)) + goto fail; + + if (bi.biWidth <= 0) + { + WLog_WARN(TAG, "bi.biWidth=%" PRId32, bi.biWidth); + goto fail; + } + + image->width = (UINT32)bi.biWidth; + + if (bi.biHeight < 0) + { + vFlip = FALSE; + image->height = (UINT32)(-1 * bi.biHeight); + } + else + { + vFlip = TRUE; + image->height = (UINT32)bi.biHeight; + } + + if (image->height <= 0) + { + WLog_WARN(TAG, "image->height=%" PRIu32, image->height); + goto fail; + } + + image->bitsPerPixel = bi.biBitCount; + image->bytesPerPixel = (image->bitsPerPixel / 8); + image->scanline = ((((bi.biWidth * bi.biBitCount) + 31) & ~31) >> 3); + const size_t bmpsize = 1ull * image->scanline * image->height; + if (bmpsize != bi.biSizeImage) + WLog_WARN(TAG, "bmpsize=%" PRIuz " != bi.biSizeImage=%" PRIu32, bmpsize, bi.biSizeImage); + if (bi.biSizeImage < bmpsize) + goto fail; + + image->data = (BYTE*)malloc(bi.biSizeImage); + + if (!image->data) + goto fail; + + if (!vFlip) + Stream_Read(s, image->data, bi.biSizeImage); + else + { + BYTE* pDstData = &(image->data[(image->height - 1ull) * image->scanline]); + + for (size_t index = 0; index < image->height; index++) + { + Stream_Read(s, pDstData, image->scanline); + pDstData -= image->scanline; + } + } + + rc = 1; +fail: + + if (rc < 0) + { + free(image->data); + image->data = NULL; + } + + return rc; +} + +int winpr_image_read(wImage* image, const char* filename) +{ + int status = -1; + + FILE* fp = winpr_fopen(filename, "rb"); + if (!fp) + { + WLog_ERR(TAG, "failed to open file %s", filename); + return -1; + } + + fseek(fp, 0, SEEK_END); + INT64 pos = _ftelli64(fp); + fseek(fp, 0, SEEK_SET); + + if (pos > 0) + { + char* buffer = malloc((size_t)pos); + if (buffer) + { + size_t r = fread(buffer, 1, (size_t)pos, fp); + if (r == (size_t)pos) + { + status = winpr_image_read_buffer(image, buffer, (size_t)pos); + } + } + free(buffer); + } + fclose(fp); + return status; +} + +int winpr_image_read_buffer(wImage* image, const BYTE* buffer, size_t size) +{ + BYTE sig[12] = { 0 }; + int status = -1; + + if (size < sizeof(sig)) + return -1; + + CopyMemory(sig, buffer, sizeof(sig)); + + if ((sig[0] == 'B') && (sig[1] == 'M')) + { + image->type = WINPR_IMAGE_BITMAP; + status = winpr_image_bitmap_read_buffer(image, buffer, size); + } + else if ((sig[0] == 'R') && (sig[1] == 'I') && (sig[2] == 'F') && (sig[3] == 'F') && + (sig[8] == 'W') && (sig[9] == 'E') && (sig[10] == 'B') && (sig[11] == 'P')) + { + image->type = WINPR_IMAGE_WEBP; + const SSIZE_T rc = + winpr_convert_from_webp((const char*)buffer, size, &image->width, &image->height, + &image->bitsPerPixel, ((char**)&image->data)); + if (rc >= 0) + { + image->bytesPerPixel = (image->bitsPerPixel + 7) / 8; + image->scanline = image->width * image->bytesPerPixel; + status = 1; + } + } + else if ((sig[0] == 0xFF) && (sig[1] == 0xD8) && (sig[2] == 0xFF) && (sig[3] == 0xE0) && + (sig[6] == 0x4A) && (sig[7] == 0x46) && (sig[8] == 0x49) && (sig[9] == 0x46) && + (sig[10] == 0x00)) + { + image->type = WINPR_IMAGE_JPEG; + const SSIZE_T rc = + winpr_convert_from_jpeg((const char*)buffer, size, &image->width, &image->height, + &image->bitsPerPixel, ((char**)&image->data)); + if (rc >= 0) + { + image->bytesPerPixel = (image->bitsPerPixel + 7) / 8; + image->scanline = image->width * image->bytesPerPixel; + status = 1; + } + } + else if ((sig[0] == 0x89) && (sig[1] == 'P') && (sig[2] == 'N') && (sig[3] == 'G') && + (sig[4] == '\r') && (sig[5] == '\n') && (sig[6] == 0x1A) && (sig[7] == '\n')) + { + image->type = WINPR_IMAGE_PNG; + const SSIZE_T rc = + winpr_convert_from_png((const char*)buffer, size, &image->width, &image->height, + &image->bitsPerPixel, ((char**)&image->data)); + if (rc >= 0) + { + image->bytesPerPixel = (image->bitsPerPixel + 7) / 8; + image->scanline = image->width * image->bytesPerPixel; + status = 1; + } + } + + return status; +} + +wImage* winpr_image_new(void) +{ + wImage* image = (wImage*)calloc(1, sizeof(wImage)); + + if (!image) + return NULL; + + return image; +} + +void winpr_image_free(wImage* image, BOOL bFreeBuffer) +{ + if (!image) + return; + + if (bFreeBuffer) + free(image->data); + + free(image); +} + +void* winpr_convert_to_jpeg(const void* data, size_t size, UINT32 width, UINT32 height, + UINT32 stride, UINT32 bpp, UINT32* pSize) +{ + WINPR_ASSERT(data || (size == 0)); + WINPR_ASSERT(pSize); + + *pSize = 0; + +#if !defined(WINPR_UTILS_IMAGE_JPEG) + return NULL; +#else + BYTE* outbuffer = NULL; + unsigned long outsize = 0; + struct jpeg_compress_struct cinfo = { 0 }; + + const size_t expect1 = 1ull * stride * height; + const size_t bytes = (bpp + 7) / 8; + const size_t expect2 = 1ull * width * height * bytes; + if (expect1 != expect2) + return NULL; + if (expect1 > size) + return NULL; + + /* Set up the error handler. */ + struct jpeg_error_mgr jerr = { 0 }; + cinfo.err = jpeg_std_error(&jerr); + + jpeg_create_compress(&cinfo); + jpeg_mem_dest(&cinfo, &outbuffer, &outsize); + + cinfo.image_width = width; + cinfo.image_height = height; + cinfo.input_components = (bpp + 7) / 8; + cinfo.in_color_space = (bpp > 24) ? JCS_EXT_BGRA : JCS_EXT_BGR; + cinfo.data_precision = 8; + + jpeg_set_defaults(&cinfo); + jpeg_set_quality(&cinfo, 100, TRUE); + /* Use 4:4:4 subsampling (default is 4:2:0) */ + cinfo.comp_info[0].h_samp_factor = cinfo.comp_info[0].v_samp_factor = 1; + + jpeg_start_compress(&cinfo, TRUE); + + const unsigned char* cdata = data; + for (size_t x = 0; x < height; x++) + { + const JDIMENSION offset = x * stride; + const JSAMPROW coffset = &cdata[offset]; + if (jpeg_write_scanlines(&cinfo, &coffset, 1) != 1) + goto fail; + } + +fail: + jpeg_finish_compress(&cinfo); + jpeg_destroy_compress(&cinfo); + + *pSize = outsize; + return outbuffer; +#endif +} + +SSIZE_T winpr_convert_from_jpeg(const char* comp_data, size_t comp_data_bytes, UINT32* width, + UINT32* height, UINT32* bpp, char** ppdecomp_data) +{ + WINPR_ASSERT(comp_data || (comp_data_bytes == 0)); + WINPR_ASSERT(width); + WINPR_ASSERT(height); + WINPR_ASSERT(bpp); + WINPR_ASSERT(ppdecomp_data); + +#if !defined(WINPR_UTILS_IMAGE_JPEG) + return -1; +#else + struct jpeg_decompress_struct cinfo = { 0 }; + struct jpeg_error_mgr jerr; + SSIZE_T size = -1; + char* decomp_data = NULL; + + cinfo.err = jpeg_std_error(&jerr); + jpeg_create_decompress(&cinfo); + jpeg_mem_src(&cinfo, comp_data, comp_data_bytes); + + if (jpeg_read_header(&cinfo, 1) != JPEG_HEADER_OK) + goto fail; + + cinfo.out_color_space = cinfo.num_components > 3 ? JCS_EXT_RGBA : JCS_EXT_BGR; + + *width = cinfo.image_width; + *height = cinfo.image_height; + *bpp = cinfo.num_components * 8; + + if (!jpeg_start_decompress(&cinfo)) + goto fail; + + size_t stride = cinfo.image_width * cinfo.num_components; + + decomp_data = calloc(stride, cinfo.image_height); + if (decomp_data) + { + while (cinfo.output_scanline < cinfo.image_height) + { + JSAMPROW row = &decomp_data[cinfo.output_scanline * stride]; + if (jpeg_read_scanlines(&cinfo, &row, 1) != 1) + goto fail; + } + size = stride * cinfo.image_height; + } + jpeg_finish_decompress(&cinfo); + +fail: + jpeg_destroy_decompress(&cinfo); + *ppdecomp_data = decomp_data; + return size; +#endif +} + +void* winpr_convert_to_webp(const void* data, size_t size, UINT32 width, UINT32 height, + UINT32 stride, UINT32 bpp, UINT32* pSize) +{ + WINPR_ASSERT(data || (size == 0)); + WINPR_ASSERT(pSize); + + *pSize = 0; + +#if !defined(WINPR_UTILS_IMAGE_WEBP) + return NULL; +#else + size_t dstSize = 0; + uint8_t* pDstData = NULL; + switch (bpp) + { + case 32: + dstSize = WebPEncodeLosslessBGRA(data, width, height, stride, &pDstData); + break; + case 24: + dstSize = WebPEncodeLosslessBGR(data, width, height, stride, &pDstData); + break; + default: + return NULL; + } + + void* rc = malloc(dstSize); + if (rc) + { + memcpy(rc, pDstData, dstSize); + *pSize = dstSize; + } + WebPFree(pDstData); + return rc; +#endif +} + +SSIZE_T winpr_convert_from_webp(const char* comp_data, size_t comp_data_bytes, UINT32* width, + UINT32* height, UINT32* bpp, char** ppdecomp_data) +{ + WINPR_ASSERT(comp_data || (comp_data_bytes == 0)); + WINPR_ASSERT(width); + WINPR_ASSERT(height); + WINPR_ASSERT(bpp); + WINPR_ASSERT(ppdecomp_data); + + *width = 0; + *height = 0; + *bpp = 0; + *ppdecomp_data = NULL; +#if !defined(WINPR_UTILS_IMAGE_WEBP) + return -1; +#else + + uint8_t* dst = WebPDecodeBGRA(comp_data, comp_data_bytes, width, height); + if (!dst) + return -1; + + *bpp = 32; + *ppdecomp_data = dst; + return (*width) * (*height) * 4; +#endif +} + +#if defined(WINPR_UTILS_IMAGE_PNG) +struct png_mem_encode +{ + char* buffer; + size_t size; +}; + +static void png_write_data(png_structp png_ptr, png_bytep data, png_size_t length) +{ + /* with libpng15 next line causes pointer deference error; use libpng12 */ + struct png_mem_encode* p = + (struct png_mem_encode*)png_get_io_ptr(png_ptr); /* was png_ptr->io_ptr */ + size_t nsize = p->size + length; + + /* allocate or grow buffer */ + if (p->buffer) + p->buffer = realloc(p->buffer, nsize); + else + p->buffer = malloc(nsize); + + if (!p->buffer) + png_error(png_ptr, "Write Error"); + + /* copy new bytes to end of buffer */ + memcpy(p->buffer + p->size, data, length); + p->size += length; +} + +/* This is optional but included to show how png_set_write_fn() is called */ +static void png_flush(png_structp png_ptr) +{ +} + +static SSIZE_T save_png_to_buffer(UINT32 bpp, UINT32 width, UINT32 height, const uint8_t* data, + size_t size, void** pDstData) +{ + int rc = -1; + png_structp png_ptr = NULL; + png_infop info_ptr = NULL; + png_byte** row_pointers = NULL; + struct png_mem_encode state = { 0 }; + + *pDstData = NULL; + + if (!data || (size == 0)) + return 0; + + WINPR_ASSERT(pDstData); + + const size_t bytes_per_pixel = (bpp + 7) / 8; + const size_t bytes_per_row = width * bytes_per_pixel; + if (size < bytes_per_row * height) + goto fail; + + /* Initialize the write struct. */ + png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); + if (png_ptr == NULL) + goto fail; + + /* Initialize the info struct. */ + info_ptr = png_create_info_struct(png_ptr); + if (info_ptr == NULL) + goto fail; + + /* Set up error handling. */ + if (setjmp(png_jmpbuf(png_ptr))) + goto fail; + + /* Set image attributes. */ + int colorType = PNG_COLOR_TYPE_PALETTE; + if (bpp > 8) + colorType = PNG_COLOR_TYPE_RGB; + if (bpp > 16) + colorType = PNG_COLOR_TYPE_RGB; + if (bpp > 24) + colorType = PNG_COLOR_TYPE_RGBA; + + png_set_IHDR(png_ptr, info_ptr, width, height, 8, colorType, PNG_INTERLACE_NONE, + PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT); + + /* Initialize rows of PNG. */ + row_pointers = png_malloc(png_ptr, height * sizeof(png_byte*)); + for (size_t y = 0; y < height; ++y) + { + uint8_t* row = png_malloc(png_ptr, sizeof(uint8_t) * bytes_per_row); + row_pointers[y] = (png_byte*)row; + for (size_t x = 0; x < width; ++x) + { + + *row++ = *data++; + if (bpp > 8) + *row++ = *data++; + if (bpp > 16) + *row++ = *data++; + if (bpp > 24) + *row++ = *data++; + } + } + + /* Actually write the image data. */ + png_set_write_fn(png_ptr, &state, png_write_data, png_flush); + png_set_rows(png_ptr, info_ptr, row_pointers); + png_write_png(png_ptr, info_ptr, PNG_TRANSFORM_BGR, NULL); + + /* Cleanup. */ + for (size_t y = 0; y < height; y++) + png_free(png_ptr, row_pointers[y]); + png_free(png_ptr, row_pointers); + + /* Finish writing. */ + rc = state.size; + *pDstData = state.buffer; +fail: + png_destroy_write_struct(&png_ptr, &info_ptr); + if (rc < 0) + free(state.buffer); + return rc; +} + +typedef struct +{ + png_bytep buffer; + png_uint_32 bufsize; + png_uint_32 current_pos; +} MEMORY_READER_STATE; + +static void read_data_memory(png_structp png_ptr, png_bytep data, size_t length) +{ + MEMORY_READER_STATE* f = png_get_io_ptr(png_ptr); + if (length > (f->bufsize - f->current_pos)) + png_error(png_ptr, "read error in read_data_memory (loadpng)"); + else + { + memcpy(data, f->buffer + f->current_pos, length); + f->current_pos += length; + } +} + +static void* winpr_read_png_from_buffer(const void* data, size_t SrcSize, size_t* pSize, + UINT32* pWidth, UINT32* pHeight, UINT32* pBpp) +{ + void* rc = NULL; + png_uint_32 width = 0; + png_uint_32 height = 0; + int bit_depth = 0; + int color_type = 0; + int interlace_type = 0; + int transforms = PNG_TRANSFORM_IDENTITY; + MEMORY_READER_STATE memory_reader_state = { 0 }; + png_bytepp row_pointers = NULL; + png_infop info_ptr = NULL; + png_structp png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); + if (!png_ptr) + goto fail; + info_ptr = png_create_info_struct(png_ptr); + if (!info_ptr) + goto fail; + + memory_reader_state.buffer = (png_bytep)data; + memory_reader_state.bufsize = SrcSize; + memory_reader_state.current_pos = 0; + + png_set_read_fn(png_ptr, &memory_reader_state, read_data_memory); + + transforms |= PNG_TRANSFORM_BGR; + png_read_png(png_ptr, info_ptr, transforms, NULL); + + if (png_get_IHDR(png_ptr, info_ptr, &width, &height, &bit_depth, &color_type, &interlace_type, + NULL, NULL) != 1) + goto fail; + + size_t bpp = PNG_IMAGE_PIXEL_SIZE(color_type); + + row_pointers = png_get_rows(png_ptr, info_ptr); + if (row_pointers) + { + const size_t stride = width * bpp; + const size_t png_stride = png_get_rowbytes(png_ptr, info_ptr); + const size_t size = width * height * bpp; + const size_t copybytes = stride > png_stride ? png_stride : stride; + + rc = malloc(size); + if (rc) + { + char* cur = rc; + for (int i = 0; i < height; i++) + { + memcpy(cur, row_pointers[i], copybytes); + cur += stride; + } + *pSize = size; + *pWidth = width; + *pHeight = height; + *pBpp = bpp * 8; + } + } +fail: + + png_destroy_read_struct(&png_ptr, &info_ptr, NULL); + return rc; +} +#endif + +void* winpr_convert_to_png(const void* data, size_t size, UINT32 width, UINT32 height, + UINT32 stride, UINT32 bpp, UINT32* pSize) +{ + WINPR_ASSERT(data || (size == 0)); + WINPR_ASSERT(pSize); + + *pSize = 0; + +#if defined(WINPR_UTILS_IMAGE_PNG) + void* dst = NULL; + SSIZE_T rc = save_png_to_buffer(bpp, width, height, data, size, &dst); + if (rc <= 0) + return NULL; + *pSize = (UINT32)rc; + return dst; +#elif defined(WITH_LODEPNG) + { + BYTE* dst = NULL; + size_t dstsize = 0; + unsigned rc = 1; + + switch (bpp) + { + case 32: + rc = lodepng_encode32(&dst, &dstsize, data, width, height); + break; + case 24: + rc = lodepng_encode24(&dst, &dstsize, data, width, height); + break; + default: + break; + } + if (rc) + return NULL; + *pSize = (UINT32)dstsize; + return dst; + } +#else + return NULL; +#endif +} + +SSIZE_T winpr_convert_from_png(const char* comp_data, size_t comp_data_bytes, UINT32* width, + UINT32* height, UINT32* bpp, char** ppdecomp_data) +{ +#if defined(WINPR_UTILS_IMAGE_PNG) + size_t len = 0; + *ppdecomp_data = + winpr_read_png_from_buffer(comp_data, comp_data_bytes, &len, width, height, bpp); + if (!*ppdecomp_data) + return -1; + return (SSIZE_T)len; +#elif defined(WITH_LODEPNG) + *bpp = 32; + return lodepng_decode32((unsigned char**)ppdecomp_data, width, height, comp_data, + comp_data_bytes); +#else + return -1; +#endif +} + +BOOL winpr_image_format_is_supported(UINT32 format) +{ + switch (format) + { + case WINPR_IMAGE_BITMAP: +#if defined(WINPR_UTILS_IMAGE_PNG) || defined(WITH_LODEPNG) + case WINPR_IMAGE_PNG: +#endif +#if defined(WINPR_UTILS_IMAGE_JPEG) + case WINPR_IMAGE_JPEG: +#endif +#if defined(WINPR_UTILS_IMAGE_WEBP) + case WINPR_IMAGE_WEBP: +#endif + return TRUE; + default: + return FALSE; + } +} + +static BYTE* convert(const wImage* image, size_t* pstride, UINT32 flags) +{ + WINPR_ASSERT(image); + WINPR_ASSERT(pstride); + + *pstride = 0; + if (image->bitsPerPixel < 24) + return NULL; + + const size_t stride = image->width * 4ull; + BYTE* data = calloc(stride, image->height); + if (data) + { + for (size_t y = 0; y < image->height; y++) + { + const BYTE* srcLine = &image->data[image->scanline * y]; + BYTE* dstLine = &data[stride * y]; + if (image->bitsPerPixel == 32) + memcpy(dstLine, srcLine, stride); + else + { + for (size_t x = 0; x < image->width; x++) + { + const BYTE* src = &srcLine[image->bytesPerPixel * x]; + BYTE* dst = &dstLine[4ull * x]; + BYTE b = *src++; + BYTE g = *src++; + BYTE r = *src++; + + *dst++ = b; + *dst++ = g; + *dst++ = r; + *dst++ = 0xff; + } + } + } + *pstride = stride; + } + return data; +} + +static BOOL compare_byte_relaxed(BYTE a, BYTE b, UINT32 flags) +{ + if (a != b) + { + if ((flags & WINPR_IMAGE_CMP_FUZZY) != 0) + { + const int diff = abs((int)a) - abs((int)b); + /* filter out quantization errors */ + if (diff > 6) + return FALSE; + } + else + { + return FALSE; + } + } + return TRUE; +} + +static BOOL compare_pixel(const BYTE* pa, const BYTE* pb, UINT32 flags) +{ + WINPR_ASSERT(pa); + WINPR_ASSERT(pb); + + if (!compare_byte_relaxed(*pa++, *pb++, flags)) + return FALSE; + if (!compare_byte_relaxed(*pa++, *pb++, flags)) + return FALSE; + if (!compare_byte_relaxed(*pa++, *pb++, flags)) + return FALSE; + if ((flags & WINPR_IMAGE_CMP_IGNORE_ALPHA) == 0) + { + if (!compare_byte_relaxed(*pa++, *pb++, flags)) + return FALSE; + } + return TRUE; +} + +BOOL winpr_image_equal(const wImage* imageA, const wImage* imageB, UINT32 flags) +{ + if (imageA == imageB) + return TRUE; + if (!imageA || !imageB) + return FALSE; + + if (imageA->height != imageB->height) + return FALSE; + if (imageA->width != imageB->width) + return FALSE; + + if ((flags & WINPR_IMAGE_CMP_IGNORE_DEPTH) == 0) + { + if (imageA->bitsPerPixel != imageB->bitsPerPixel) + return FALSE; + if (imageA->bytesPerPixel != imageB->bytesPerPixel) + return FALSE; + } + + BOOL rc = FALSE; + size_t astride = 0; + size_t bstride = 0; + BYTE* dataA = convert(imageA, &astride, flags); + BYTE* dataB = convert(imageA, &bstride, flags); + if (dataA && dataB && (astride == bstride)) + { + rc = TRUE; + for (size_t y = 0; y < imageA->height; y++) + { + const BYTE* lineA = &dataA[astride * y]; + const BYTE* lineB = &dataB[bstride * y]; + + for (size_t x = 0; x < imageA->width; x++) + { + const BYTE* pa = &lineA[x * 4ull]; + const BYTE* pb = &lineB[x * 4ull]; + + if (!compare_pixel(pa, pb, flags)) + rc = FALSE; + } + } + } + free(dataA); + free(dataB); + return rc; +} + +const char* winpr_image_format_mime(UINT32 format) +{ + switch (format) + { + case WINPR_IMAGE_BITMAP: + return "image/bmp"; + case WINPR_IMAGE_PNG: + return "image/png"; + case WINPR_IMAGE_WEBP: + return "image/webp"; + case WINPR_IMAGE_JPEG: + return "image/jpeg"; + default: + return NULL; + } +} + +const char* winpr_image_format_extension(UINT32 format) +{ + switch (format) + { + case WINPR_IMAGE_BITMAP: + return "bmp"; + case WINPR_IMAGE_PNG: + return "png"; + case WINPR_IMAGE_WEBP: + return "webp"; + case WINPR_IMAGE_JPEG: + return "jpg"; + default: + return NULL; + } +} + +void* winpr_image_write_buffer(wImage* image, UINT32 format, size_t* psize) +{ + WINPR_ASSERT(image); + switch (format) + { + case WINPR_IMAGE_BITMAP: + { + UINT32 outsize = 0; + size_t size = 1ull * image->height * image->scanline; + void* data = winpr_bitmap_write_buffer(image->data, size, image->width, image->height, + image->scanline, image->bitsPerPixel, &outsize); + *psize = outsize; + return data; + } + break; + case WINPR_IMAGE_WEBP: + { + UINT32 outsize = 0; + size_t size = 1ull * image->height * image->scanline; + void* data = winpr_convert_to_webp(image->data, size, image->width, image->height, + image->scanline, image->bitsPerPixel, &outsize); + *psize = outsize; + return data; + } + break; + case WINPR_IMAGE_JPEG: + { + UINT32 outsize = 0; + size_t size = 1ull * image->height * image->scanline; + void* data = winpr_convert_to_jpeg(image->data, size, image->width, image->height, + image->scanline, image->bitsPerPixel, &outsize); + *psize = outsize; + return data; + } + break; + case WINPR_IMAGE_PNG: + { + UINT32 outsize = 0; + size_t size = 1ull * image->height * image->scanline; + void* data = winpr_convert_to_png(image->data, size, image->width, image->height, + image->scanline, image->bitsPerPixel, &outsize); + *psize = outsize; + return data; + } + break; + default: + *psize = 0; + return NULL; + } +} |