blob: 61ed7a4a85fec4fab74c8e0d6d02decb50637ab3 [file]
/* -*- C++ -*-
* Copyright 2019-2025 LibRaw LLC (info@libraw.org)
*
LibRaw is free software; you can redistribute it and/or modify
it under the terms of the one of two licenses as you choose:
1. GNU LESSER GENERAL PUBLIC LICENSE version 2.1
(See file LICENSE.LGPL provided in LibRaw distribution archive for details).
2. COMMON DEVELOPMENT AND DISTRIBUTION LICENSE (CDDL) Version 1.0
(See file LICENSE.CDDL provided in LibRaw distribution archive for details).
*/
#include "third_party/libraw/internal/libraw_cxx_defs.h"
#ifndef NO_JPEG
struct jpegErrorManager
{
struct jpeg_error_mgr pub;
jmp_buf setjmp_buffer;
};
static void jpegErrorExit(j_common_ptr cinfo)
{
jpegErrorManager *myerr = (jpegErrorManager *)cinfo->err;
longjmp(myerr->setjmp_buffer, 1);
}
#endif
int LibRaw::unpack_thumb_ex(int idx)
{
if (idx < 0 || idx >= imgdata.thumbs_list.thumbcount || idx >= LIBRAW_THUMBNAIL_MAXCOUNT)
return LIBRAW_REQUEST_FOR_NONEXISTENT_THUMBNAIL;
// Set from thumb-list
libraw_internal_data.internal_data.toffset = imgdata.thumbs_list.thumblist[idx].toffset;
imgdata.thumbnail.tlength = imgdata.thumbs_list.thumblist[idx].tlength;
libraw_internal_data.unpacker_data.thumb_format = imgdata.thumbs_list.thumblist[idx].tformat;
imgdata.thumbnail.twidth = imgdata.thumbs_list.thumblist[idx].twidth;
imgdata.thumbnail.theight = imgdata.thumbs_list.thumblist[idx].theight;
libraw_internal_data.unpacker_data.thumb_misc = imgdata.thumbs_list.thumblist[idx].tmisc;
int rc = unpack_thumb();
imgdata.progress_flags &= ~LIBRAW_PROGRESS_THUMB_LOAD;
return rc;
}
int LibRaw::unpack_thumb(void)
{
CHECK_ORDER_LOW(LIBRAW_PROGRESS_IDENTIFY);
CHECK_ORDER_BIT(LIBRAW_PROGRESS_THUMB_LOAD);
#define THUMB_SIZE_CHECKT(A) \
do { \
if (INT64(A) > 1024LL * 1024LL * LIBRAW_MAX_THUMBNAIL_MB) return LIBRAW_UNSUPPORTED_THUMBNAIL; \
if (INT64(A) > 0 && INT64(A) < 64LL) return LIBRAW_NO_THUMBNAIL; \
} while (0)
#define THUMB_SIZE_CHECKTNZ(A) \
do { \
if (INT64(A) > 1024LL * 1024LL * LIBRAW_MAX_THUMBNAIL_MB) return LIBRAW_UNSUPPORTED_THUMBNAIL; \
if (INT64(A) < 64LL) return LIBRAW_NO_THUMBNAIL; \
} while (0)
#define THUMB_SIZE_CHECKWH(W,H) \
do { \
if (INT64(W)*INT64(H) > 1024ULL * 1024ULL * LIBRAW_MAX_THUMBNAIL_MB) return LIBRAW_UNSUPPORTED_THUMBNAIL; \
if (INT64(W)*INT64(H) < 64ULL) return LIBRAW_NO_THUMBNAIL; \
} while (0)
#define Tformat libraw_internal_data.unpacker_data.thumb_format
try
{
if (!libraw_internal_data.internal_data.input)
return LIBRAW_INPUT_CLOSED;
int t_colors = libraw_internal_data.unpacker_data.thumb_misc >> 5 & 7;
int t_bytesps = (libraw_internal_data.unpacker_data.thumb_misc & 31) / 8;
if (!ID.toffset && !(imgdata.thumbnail.tlength > 0 &&
load_raw == &LibRaw::broadcom_load_raw) // RPi
#ifdef USE_6BY9RPI
&& !(imgdata.thumbnail.tlength > 0 && libraw_internal_data.unpacker_data.load_flags & 0x4000
&& (load_raw == &LibRaw::rpi_load_raw8 || load_raw == &LibRaw::nokia_load_raw ||
load_raw == &LibRaw::rpi_load_raw12 || load_raw == &LibRaw::rpi_load_raw14))
#endif
)
{
return LIBRAW_NO_THUMBNAIL;
}
else if (Tformat == LIBRAW_INTERNAL_THUMBNAIL_DNG_YCBCR)
{
try
{
dng_ycbcr_thumb_loader();
T.tformat = LIBRAW_THUMBNAIL_BITMAP;
T.tcolors = 3;
SET_PROC_FLAG(LIBRAW_PROGRESS_THUMB_LOAD);
}
catch (...)
{
return LIBRAW_NO_THUMBNAIL;
}
return 0;
}
else if ((Tformat >= LIBRAW_INTERNAL_THUMBNAIL_KODAK_THUMB)
&& ((Tformat <= LIBRAW_INTERNAL_THUMBNAIL_KODAK_RGB)))
{
try {
kodak_thumb_loader();
T.tformat = LIBRAW_THUMBNAIL_BITMAP;
SET_PROC_FLAG(LIBRAW_PROGRESS_THUMB_LOAD);
}
catch (...)
{
return LIBRAW_NO_THUMBNAIL;
}
return 0;
}
else
{
#ifdef USE_X3FTOOLS
if (Tformat == LIBRAW_INTERNAL_THUMBNAIL_X3F)
{
INT64 tsize = x3f_thumb_size();
if (tsize < 2048 || INT64(ID.toffset) + tsize < 1)
return LIBRAW_NO_THUMBNAIL;
if (INT64(ID.toffset) + tsize > ID.input->size() + THUMB_READ_BEYOND)
return LIBRAW_NO_THUMBNAIL;
THUMB_SIZE_CHECKT(tsize);
}
#else
if (0) {}
#endif
else
{
if (INT64(ID.toffset) + INT64(T.tlength) < 1)
return LIBRAW_NO_THUMBNAIL;
if (INT64(ID.toffset) + INT64(T.tlength) >
ID.input->size() + THUMB_READ_BEYOND)
return LIBRAW_NO_THUMBNAIL;
}
ID.input->seek(ID.toffset, SEEK_SET);
if (Tformat == LIBRAW_INTERNAL_THUMBNAIL_JPEG || Tformat == LIBRAW_INTERNAL_THUMBNAIL_JPEGXL)
{
THUMB_SIZE_CHECKTNZ(T.tlength);
if (T.thumb)
free(T.thumb);
#ifdef LIBRAW_CALLOC_RAWSTORE
T.thumb = (char *)calloc(T.tlength,1);
#else
T.thumb = (char *)malloc(T.tlength);
#endif
if(!T.thumb)
return LIBRAW_NO_THUMBNAIL;
ID.input->read(T.thumb, 1, T.tlength);
unsigned char *tthumb = (unsigned char *)T.thumb;
if (Tformat == LIBRAW_INTERNAL_THUMBNAIL_JPEGXL)
{
T.tformat = LIBRAW_THUMBNAIL_JPEGXL;
SET_PROC_FLAG(LIBRAW_PROGRESS_THUMB_LOAD);
return 0;
}
if (load_raw == &LibRaw::crxLoadRaw && T.tlength > 0xE0)
{
// Check if it is canon H.265 preview: CISZ at bytes 4-6, CISZ prefix is 000n
if (tthumb[0] == 0 && tthumb[1] == 0 && tthumb[2] == 0 && !memcmp(tthumb + 4, "CISZ", 4))
{
T.tformat = LIBRAW_THUMBNAIL_H265;
SET_PROC_FLAG(LIBRAW_PROGRESS_THUMB_LOAD);
return 0;
}
}
tthumb[0] = 0xff;
tthumb[1] = 0xd8;
#ifdef NO_JPEG
T.tcolors = 3;
#else
{
jpegErrorManager jerr;
struct jpeg_decompress_struct cinfo;
cinfo.err = jpeg_std_error(&jerr.pub);
jerr.pub.error_exit = jpegErrorExit;
if (setjmp(jerr.setjmp_buffer))
{
err2:
// Error in original JPEG thumb, read it again because
// original bytes 0-1 was damaged above
jpeg_destroy_decompress(&cinfo);
T.tcolors = 3;
T.tformat = LIBRAW_THUMBNAIL_UNKNOWN;
ID.input->seek(ID.toffset, SEEK_SET);
ID.input->read(T.thumb, 1, T.tlength);
SET_PROC_FLAG(LIBRAW_PROGRESS_THUMB_LOAD);
return 0;
}
jpeg_create_decompress(&cinfo);
jpeg_mem_src(&cinfo, (unsigned char *)T.thumb, T.tlength);
int rc = jpeg_read_header(&cinfo, TRUE);
if (rc != 1)
goto err2;
T.tcolors = (cinfo.num_components > 0 && cinfo.num_components <= 3)
? cinfo.num_components
: 3;
jpeg_destroy_decompress(&cinfo);
}
#endif
T.tformat = LIBRAW_THUMBNAIL_JPEG;
SET_PROC_FLAG(LIBRAW_PROGRESS_THUMB_LOAD);
return 0;
}
else if (Tformat == LIBRAW_INTERNAL_THUMBNAIL_LAYER)
{
int colors = libraw_internal_data.unpacker_data.thumb_misc >> 5 & 7;
if (colors != 1 && colors != 3)
return LIBRAW_UNSUPPORTED_THUMBNAIL;
THUMB_SIZE_CHECKWH(T.twidth, T.theight);
int tlength = T.twidth * T.theight;
if (T.thumb)
free(T.thumb);
T.thumb = (char *)calloc(colors, tlength);
if(!T.thumb)
return LIBRAW_NO_THUMBNAIL;
unsigned char *tbuf = (unsigned char *)calloc(colors, tlength);
if (!tbuf)
{
free(T.thumb);
T.thumb = 0;
return LIBRAW_NO_THUMBNAIL;
}
// Avoid OOB of tbuf, should use tlength
ID.input->read(tbuf, colors, tlength);
if (libraw_internal_data.unpacker_data.thumb_misc >> 8 &&
colors == 3) // GRB order
for (int i = 0; i < tlength; i++)
{
T.thumb[i * 3] = tbuf[i + tlength];
T.thumb[i * 3 + 1] = tbuf[i];
T.thumb[i * 3 + 2] = tbuf[i + 2 * tlength];
}
else if (colors == 3) // RGB or 1-channel
for (int i = 0; i < tlength; i++)
{
T.thumb[i * 3] = tbuf[i];
T.thumb[i * 3 + 1] = tbuf[i + tlength];
T.thumb[i * 3 + 2] = tbuf[i + 2 * tlength];
}
else if (colors == 1)
{
free(T.thumb);
T.thumb = (char *)tbuf;
tbuf = 0;
}
if (tbuf)
free(tbuf);
T.tcolors = colors;
T.tlength = colors * tlength;
T.tformat = LIBRAW_THUMBNAIL_BITMAP;
SET_PROC_FLAG(LIBRAW_PROGRESS_THUMB_LOAD);
return 0;
}
else if (Tformat == LIBRAW_INTERNAL_THUMBNAIL_ROLLEI)
{
int i;
THUMB_SIZE_CHECKWH(T.twidth, T.theight);
int tlength = T.twidth * T.theight;
if (T.thumb)
free(T.thumb);
T.tcolors = 3;
T.thumb = (char *)calloc(T.tcolors, tlength);
if (!T.thumb)
return LIBRAW_NO_THUMBNAIL;
unsigned short *tbuf = (unsigned short *)calloc(2, tlength);
if (!tbuf)
{
free(T.thumb);
T.thumb = 0;
return LIBRAW_NO_THUMBNAIL;
}
try {
read_shorts(tbuf, tlength);
for (i = 0; i < tlength; i++)
{
T.thumb[i * 3] = (tbuf[i] << 3) & 0xff;
T.thumb[i * 3 + 1] = (tbuf[i] >> 5 << 2) & 0xff;
T.thumb[i * 3 + 2] = (tbuf[i] >> 11 << 3) & 0xff;
}
free(tbuf);
T.tlength = T.tcolors * tlength;
T.tformat = LIBRAW_THUMBNAIL_BITMAP;
SET_PROC_FLAG(LIBRAW_PROGRESS_THUMB_LOAD);
}
catch (...)
{
free(tbuf);
return LIBRAW_NO_THUMBNAIL;
}
return 0;
}
else if (Tformat == LIBRAW_INTERNAL_THUMBNAIL_PPM)
{
if (t_bytesps > 1)
return LIBRAW_NO_THUMBNAIL; // 8-bit thumb, but parsed for more
// bits
THUMB_SIZE_CHECKWH(T.twidth, T.theight);
int t_length = T.twidth * T.theight * t_colors;
if (T.tlength &&
(int)T.tlength < t_length) // try to find tiff ifd with needed offset
{
int pifd = find_ifd_by_offset(libraw_internal_data.internal_data.toffset);
if (pifd >= 0 && tiff_ifd[pifd].strip_offsets_count &&
tiff_ifd[pifd].strip_byte_counts_count)
{
// We found it, calculate final size
INT64 total_size = 0;
for (int i = 0; i < tiff_ifd[pifd].strip_byte_counts_count
&& i < tiff_ifd[pifd].strip_offsets_count; i++)
total_size += tiff_ifd[pifd].strip_byte_counts[i];
if (total_size != (unsigned)t_length) // recalculate colors
{
if (total_size == T.twidth * T.tlength * 3)
T.tcolors = 3;
else if (total_size == T.twidth * T.tlength)
T.tcolors = 1;
}
T.tlength = unsigned(total_size);
THUMB_SIZE_CHECKTNZ(T.tlength);
if (T.thumb)
free(T.thumb);
#ifdef LIBRAW_CALLOC_RAWSTORE
T.thumb = (char *)calloc(T.tlength,1);
#else
T.thumb = (char *)malloc(T.tlength);
#endif
if (!T.thumb)
return LIBRAW_NO_THUMBNAIL;
char *dest = T.thumb;
INT64 pos = ID.input->tell();
INT64 remain = T.tlength;
for (int i = 0; i < tiff_ifd[pifd].strip_byte_counts_count &&
i < tiff_ifd[pifd].strip_offsets_count;
i++)
{
int sz = tiff_ifd[pifd].strip_byte_counts[i];
INT64 off = tiff_ifd[pifd].strip_offsets[i];
if (off >= 0 && off + sz <= ID.input->size() && sz > 0 && INT64(sz) <= remain)
{
ID.input->seek(off, SEEK_SET);
ID.input->read(dest, sz, 1);
remain -= sz;
dest += sz;
}
}
ID.input->seek(pos, SEEK_SET);
T.tformat = LIBRAW_THUMBNAIL_BITMAP;
SET_PROC_FLAG(LIBRAW_PROGRESS_THUMB_LOAD);
return 0;
}
}
if (!T.tlength)
T.tlength = t_length;
if (T.thumb)
free(T.thumb);
THUMB_SIZE_CHECKTNZ(T.tlength);
#ifdef LIBRAW_CALLOC_RAWSTORE
T.thumb = (char *)calloc(T.tlength,1);
#else
T.thumb = (char *)malloc(T.tlength);
#endif
if (!T.thumb)
return LIBRAW_NO_THUMBNAIL;
if (!T.tcolors)
T.tcolors = t_colors;
ID.input->read(T.thumb, 1, T.tlength);
T.tformat = LIBRAW_THUMBNAIL_BITMAP;
SET_PROC_FLAG(LIBRAW_PROGRESS_THUMB_LOAD);
return 0;
}
else if (Tformat == LIBRAW_INTERNAL_THUMBNAIL_PPM16)
{
if (t_bytesps > 2)
return LIBRAW_NO_THUMBNAIL; // 16-bit thumb, but parsed for
// more bits
int o_bps = (imgdata.rawparams.options & LIBRAW_RAWOPTIONS_USE_PPM16_THUMBS) ? 2 : 1;
int o_length = T.twidth * T.theight * t_colors * o_bps;
int i_length = T.twidth * T.theight * t_colors * 2;
THUMB_SIZE_CHECKTNZ(o_length);
THUMB_SIZE_CHECKTNZ(i_length);
ushort *t_thumb = (ushort *)calloc(i_length, 1);
if (!t_thumb)
return LIBRAW_NO_THUMBNAIL;;
ID.input->read(t_thumb, 1, i_length);
if ((libraw_internal_data.unpacker_data.order == 0x4949) ==
(ntohs(0x1234) == 0x1234))
libraw_swab(t_thumb, i_length);
if (T.thumb)
free(T.thumb);
if ((imgdata.rawparams.options & LIBRAW_RAWOPTIONS_USE_PPM16_THUMBS))
{
T.thumb = (char *)t_thumb;
T.tformat = LIBRAW_THUMBNAIL_BITMAP16;
T.tlength = i_length;
}
else
{
#ifdef LIBRAW_CALLOC_RAWSTORE
T.thumb = (char *)calloc(o_length,1);
#else
T.thumb = (char *)malloc(o_length);
#endif
if (!T.thumb)
{
free(t_thumb);
return LIBRAW_NO_THUMBNAIL;
}
for (int i = 0; i < o_length; i++)
T.thumb[i] = t_thumb[i] >> 8;
free(t_thumb);
T.tformat = LIBRAW_THUMBNAIL_BITMAP;
T.tlength = o_length;
}
SET_PROC_FLAG(LIBRAW_PROGRESS_THUMB_LOAD);
return 0;
}
#ifdef USE_X3FTOOLS
else if (Tformat == LIBRAW_INTERNAL_THUMBNAIL_X3F)
{
x3f_thumb_loader(); // errors already catched in this call
SET_PROC_FLAG(LIBRAW_PROGRESS_THUMB_LOAD);
if (!T.twidth && !T.theight)
return LIBRAW_NO_THUMBNAIL;
return 0;
}
#endif
else
{
return LIBRAW_UNSUPPORTED_THUMBNAIL;
}
}
// last resort
return LIBRAW_UNSUPPORTED_THUMBNAIL; /* warned as unreachable*/
}
catch (const LibRaw_exceptions& err)
{
EXCEPTION_HANDLER(err);
}
}