Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5d6dfb9012 | ||
|
|
72518dcc1a | ||
|
|
a6027264d4 | ||
|
|
47ea774621 | ||
|
|
5ed0affc95 | ||
|
|
42e9733060 | ||
|
|
bae45287ad | ||
|
|
458257b7a3 | ||
|
|
09452b0964 | ||
|
|
c7baa057a6 | ||
|
|
c57af25d36 | ||
|
|
320ded493a |
@@ -1,11 +1,11 @@
|
||||
cmake_minimum_required(VERSION 3.29)
|
||||
|
||||
set(KF_VERSION "6.29.0") # handled by release scripts
|
||||
set(KF_DEP_VERSION "6.29.0") # handled by release scripts
|
||||
set(KF_VERSION "6.30.0") # handled by release scripts
|
||||
set(KF_DEP_VERSION "6.30.0") # handled by release scripts
|
||||
project(KImageFormats VERSION ${KF_VERSION})
|
||||
|
||||
include(FeatureSummary)
|
||||
find_package(ECM 6.29.0 NO_MODULE)
|
||||
find_package(ECM 6.30.0 NO_MODULE)
|
||||
set_package_properties(ECM PROPERTIES TYPE REQUIRED DESCRIPTION "Extra CMake Modules." URL "https://commits.kde.org/extra-cmake-modules")
|
||||
feature_summary(WHAT REQUIRED_PACKAGES_NOT_FOUND FATAL_ON_MISSING_REQUIRED_PACKAGES)
|
||||
|
||||
|
||||
@@ -125,7 +125,27 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size)
|
||||
b.open(QIODevice::ReadOnly);
|
||||
handler->setDevice(&b);
|
||||
handler->canRead();
|
||||
handler->read(&i);
|
||||
|
||||
bool is_animated = false;
|
||||
if (handler->supportsOption(QImageIOHandler::Animation)) {
|
||||
is_animated = handler->option(QImageIOHandler::Animation).toBool();
|
||||
}
|
||||
|
||||
if (is_animated) { // animated image
|
||||
if (handler->supportsOption(QImageIOHandler::Size)) {
|
||||
handler->option(QImageIOHandler::Size).toSize();
|
||||
}
|
||||
|
||||
handler->imageCount();
|
||||
handler->loopCount();
|
||||
handler->read(&i);
|
||||
handler->nextImageDelay();
|
||||
handler->currentImageNumber();
|
||||
handler->jumpToImage(0);
|
||||
handler->jumpToNextImage();
|
||||
} else { // static image
|
||||
handler->read(&i);
|
||||
}
|
||||
|
||||
delete handler;
|
||||
|
||||
|
||||
|
After Width: | Height: | Size: 199 B |
|
After Width: | Height: | Size: 242 B |
|
After Width: | Height: | Size: 234 B |
|
After Width: | Height: | Size: 232 B |
|
After Width: | Height: | Size: 221 B |
|
After Width: | Height: | Size: 222 B |
|
After Width: | Height: | Size: 243 B |
|
After Width: | Height: | Size: 228 B |
|
After Width: | Height: | Size: 244 B |
@@ -0,0 +1,7 @@
|
||||
[
|
||||
{
|
||||
"fileName": "konqi.jpg",
|
||||
"fuzziness": 72,
|
||||
"description": "Minimum fuzziness value to pass the test on all architectures."
|
||||
}
|
||||
]
|
||||
|
After Width: | Height: | Size: 362 KiB |
@@ -1160,25 +1160,19 @@ bool QAVIFHandler::jumpToNextImage()
|
||||
return false;
|
||||
}
|
||||
|
||||
avifResult decodeResult;
|
||||
|
||||
if (m_decoder->imageIndex >= 0) {
|
||||
if (m_decoder->imageCount < 2) {
|
||||
m_parseState = ParseAvifSuccess;
|
||||
// single image, there is no next image
|
||||
return false;
|
||||
}
|
||||
|
||||
if (m_decoder->imageIndex >= m_decoder->imageCount - 1) { // start from beginning
|
||||
decodeResult = avifDecoderReset(m_decoder);
|
||||
if (decodeResult != AVIF_RESULT_OK) {
|
||||
qCWarning(LOG_AVIFPLUGIN, "ERROR in avifDecoderReset: %s", avifResultToString(decodeResult));
|
||||
m_parseState = ParseAvifError;
|
||||
return false;
|
||||
}
|
||||
if (m_decoder->imageIndex >= m_decoder->imageCount - 1) {
|
||||
// do not start from beginning when at the end already
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
decodeResult = avifDecoderNextImage(m_decoder);
|
||||
avifResult decodeResult = avifDecoderNextImage(m_decoder);
|
||||
|
||||
if (decodeResult != AVIF_RESULT_OK) {
|
||||
qCWarning(LOG_AVIFPLUGIN, "ERROR: Failed to decode Next image in sequence: %s", avifResultToString(decodeResult));
|
||||
|
||||
@@ -368,7 +368,8 @@ static void readMetadata(const Imf::Header &header, QImage &image)
|
||||
|
||||
// shot metadata
|
||||
if (auto isoSpeed = header.findTypedAttribute<Imf::FloatAttribute>("isoSpeed")) {
|
||||
image.setText(QStringLiteral(META_KEY_ISOSPEEDRATINGS), QLocale::c().toString(qRound(isoSpeed->value())));
|
||||
if (auto v = qRoundOrZero(isoSpeed->value()))
|
||||
image.setText(QStringLiteral(META_KEY_ISOSPEEDRATINGS), QLocale::c().toString(v));
|
||||
}
|
||||
if (auto expTime = header.findTypedAttribute<Imf::FloatAttribute>("expTime")) {
|
||||
image.setText(QStringLiteral(META_KEY_EXPOSURETIME), QLocale::c().toString(expTime->value()));
|
||||
|
||||
@@ -642,10 +642,31 @@ bool HEIFHandler::read_crop(void *heif_handle, const void *heif_ctx, const QSize
|
||||
if (heif_item_get_properties_of_type(ctx, item_id, heif_item_property_type_transform_crop, &crop_id, 1) > 0) {
|
||||
int l = 0, t = 0, r = 0, b = 0;
|
||||
heif_item_get_property_transform_crop_borders(ctx, item_id, crop_id, size.width(), size.height(), &l, &t, &r, &b);
|
||||
crop = QRect(QPoint(t, l), size - QSize(b + t, r + l));
|
||||
|
||||
if (l >= size.width() || r >= size.width() || t >= size.height() || b >= size.height()) {
|
||||
qCWarning(LOG_HEIFPLUGIN) << "Invalid crop values: left=" << l << "top=" << t << "right=" << r << "bottom=" << b;
|
||||
}
|
||||
if (l < 0) {
|
||||
l = 0;
|
||||
}
|
||||
if (t < 0) {
|
||||
t = 0;
|
||||
}
|
||||
if (r < 0) {
|
||||
r = 0;
|
||||
}
|
||||
if (b < 0) {
|
||||
b = 0;
|
||||
}
|
||||
if (l == 0 && t == 0 && r == 0 && b == 0) {
|
||||
// no cropping needed
|
||||
return false;
|
||||
}
|
||||
crop = QRect(QPoint(l, t), size - QSize(l + r, t + b));
|
||||
return crop.isValid();
|
||||
}
|
||||
|
||||
return crop.isValid();
|
||||
return false;
|
||||
}
|
||||
|
||||
bool HEIFHandler::isSupportedBMFFType(const QByteArray &header)
|
||||
@@ -930,8 +951,8 @@ bool HEIFHandler::ensureDecoder()
|
||||
struct heif_image *img = nullptr;
|
||||
err = heif_decode_image(handle, &img, heif_colorspace_RGB, chroma, decoder_option);
|
||||
|
||||
if (err.code == heif_error_Invalid_input && err.subcode == heif_suberror_Unknown_NCLX_matrix_coefficients && img == nullptr && buffer.contains("Xiaomi")) {
|
||||
qCWarning(LOG_HEIFPLUGIN) << "Non-standard HEIF image with invalid matrix_coefficients, probably made by a Xiaomi device!";
|
||||
if (err.code == heif_error_Invalid_input && err.subcode == heif_suberror_Unknown_NCLX_matrix_coefficients && img == nullptr) {
|
||||
qCWarning(LOG_HEIFPLUGIN) << "Non-standard HEIF image with invalid matrix_coefficients!";
|
||||
|
||||
// second try to decode with strict decoding disabled
|
||||
decoder_option->strict_decoding = 0;
|
||||
|
||||
@@ -622,21 +622,20 @@ public:
|
||||
m_cparameters.tcp_rates[0] = 100.0 - (m_quality < 10 ? m_quality : 10 + (std::log10(m_quality) - 1) * 90);
|
||||
}
|
||||
|
||||
std::unique_ptr<opj_image_cmptparm_t> cmptparm(new opj_image_cmptparm_t[ncomp]);
|
||||
for (int i = 0; i < ncomp; ++i) {
|
||||
auto &&p = cmptparm.get() + i;
|
||||
memset(p, 0, sizeof(opj_image_cmptparm_t));
|
||||
p->dx = m_cparameters.subsampling_dx;
|
||||
p->dy = m_cparameters.subsampling_dy;
|
||||
p->w = image.width();
|
||||
p->h = image.height();
|
||||
p->x0 = 0;
|
||||
p->y0 = 0;
|
||||
p->prec = prec;
|
||||
p->sgnd = 0;
|
||||
std::vector<opj_image_cmptparm_t> cmptparm(ncomp);
|
||||
for (opj_image_cmptparm_t &p : cmptparm) {
|
||||
memset(&p, 0, sizeof(opj_image_cmptparm_t));
|
||||
p.dx = m_cparameters.subsampling_dx;
|
||||
p.dy = m_cparameters.subsampling_dy;
|
||||
p.w = image.width();
|
||||
p.h = image.height();
|
||||
p.x0 = 0;
|
||||
p.y0 = 0;
|
||||
p.prec = prec;
|
||||
p.sgnd = 0;
|
||||
}
|
||||
|
||||
m_jp2_image = opj_image_create(ncomp, cmptparm.get(), cs);
|
||||
m_jp2_image = opj_image_create(ncomp, cmptparm.data(), cs);
|
||||
if (m_jp2_image == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -60,7 +60,7 @@ Q_LOGGING_CATEGORY(LOG_JXLPLUGIN, "kf.imageformats.plugins.jxl", QtWarningMsg)
|
||||
void *QtJXLMemoryManagerAlloc(void *opaque, size_t size)
|
||||
{
|
||||
if (opaque) {
|
||||
size_t maxBytes = *(size_t*)opaque;
|
||||
size_t maxBytes = *(size_t *)opaque;
|
||||
if (maxBytes && size > maxBytes)
|
||||
return NULL;
|
||||
}
|
||||
@@ -75,8 +75,7 @@ void QtJXLMemoryManagerFree(void *, void *address)
|
||||
QJpegXLHandler::QJpegXLHandler()
|
||||
: m_parseState(ParseJpegXLNotParsed)
|
||||
, m_quality(90)
|
||||
, m_currentimage_index(0)
|
||||
, m_previousimage_index(-1)
|
||||
, m_currentimage_index(-1)
|
||||
, m_transformations(QImageIOHandler::TransformationNone)
|
||||
, m_decoder(nullptr)
|
||||
, m_runner(nullptr)
|
||||
@@ -183,11 +182,7 @@ bool QJpegXLHandler::ensureDecoder()
|
||||
}
|
||||
|
||||
// Creating a simple memory manager
|
||||
JxlMemoryManager memory_manager = {
|
||||
.opaque = &m_maxBytes,
|
||||
.alloc = QtJXLMemoryManagerAlloc,
|
||||
.free = QtJXLMemoryManagerFree
|
||||
};
|
||||
JxlMemoryManager memory_manager = {.opaque = &m_maxBytes, .alloc = QtJXLMemoryManagerAlloc, .free = QtJXLMemoryManagerFree};
|
||||
// Creating the decoder (it makes a deep copy of memory manager)
|
||||
m_decoder = JxlDecoderCreate(&memory_manager);
|
||||
if (!m_decoder) {
|
||||
@@ -817,17 +812,11 @@ bool QJpegXLHandler::decode_one_frame()
|
||||
exif.updateImageMetadata(m_current_image);
|
||||
}
|
||||
|
||||
m_currentimage_index++;
|
||||
m_next_image_delay = m_framedelays[m_currentimage_index];
|
||||
m_previousimage_index = m_currentimage_index;
|
||||
|
||||
if (m_framedelays.count() > 1) {
|
||||
m_currentimage_index++;
|
||||
|
||||
if (m_currentimage_index >= m_framedelays.count()) {
|
||||
if (!rewind()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (m_currentimage_index >= m_framedelays.count() - 1) {
|
||||
// all frames in animation have been read
|
||||
m_parseState = ParseJpegXLFinished;
|
||||
} else {
|
||||
@@ -847,9 +836,9 @@ bool QJpegXLHandler::read(QImage *image)
|
||||
return false;
|
||||
}
|
||||
|
||||
if (m_currentimage_index == m_previousimage_index) {
|
||||
*image = m_current_image;
|
||||
return jumpToNextImage();
|
||||
if (m_parseState == ParseJpegXLFinished) {
|
||||
// at the end already
|
||||
return false;
|
||||
}
|
||||
|
||||
if (decode_one_frame()) {
|
||||
@@ -1757,19 +1746,26 @@ bool QJpegXLHandler::jumpToNextImage()
|
||||
}
|
||||
|
||||
if (m_framedelays.count() > 1) {
|
||||
m_currentimage_index++;
|
||||
|
||||
if (m_currentimage_index >= m_framedelays.count()) {
|
||||
if (!rewind()) {
|
||||
return false;
|
||||
}
|
||||
if (m_currentimage_index >= (m_framedelays.count() - 1)) {
|
||||
// we are already at the last frame
|
||||
return false;
|
||||
} else {
|
||||
m_currentimage_index++;
|
||||
m_next_image_delay = m_framedelays[m_currentimage_index];
|
||||
JxlDecoderSkipFrames(m_decoder, 1);
|
||||
if (m_currentimage_index >= (m_framedelays.count() - 1)) {
|
||||
// last frame reached but no more reading is possible
|
||||
m_parseState = ParseJpegXLFinished;
|
||||
} else {
|
||||
m_parseState = ParseJpegXLSuccess;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
m_parseState = ParseJpegXLSuccess;
|
||||
return true;
|
||||
// static image, there is no next frame
|
||||
return false;
|
||||
}
|
||||
|
||||
bool QJpegXLHandler::jumpToImage(int imageNumber)
|
||||
@@ -1782,14 +1778,17 @@ bool QJpegXLHandler::jumpToImage(int imageNumber)
|
||||
return false;
|
||||
}
|
||||
|
||||
if (imageNumber == m_currentimage_index) {
|
||||
const int next_image_index = m_currentimage_index + 1;
|
||||
|
||||
if (imageNumber == next_image_index) {
|
||||
// This the already the next image to read, no action
|
||||
m_parseState = ParseJpegXLSuccess;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (imageNumber > m_currentimage_index) {
|
||||
JxlDecoderSkipFrames(m_decoder, imageNumber - m_currentimage_index);
|
||||
m_currentimage_index = imageNumber;
|
||||
if (imageNumber > next_image_index) {
|
||||
JxlDecoderSkipFrames(m_decoder, imageNumber - next_image_index);
|
||||
m_currentimage_index = imageNumber - 1;
|
||||
m_parseState = ParseJpegXLSuccess;
|
||||
return true;
|
||||
}
|
||||
@@ -1801,7 +1800,7 @@ bool QJpegXLHandler::jumpToImage(int imageNumber)
|
||||
if (imageNumber > 0) {
|
||||
JxlDecoderSkipFrames(m_decoder, imageNumber);
|
||||
}
|
||||
m_currentimage_index = imageNumber;
|
||||
m_currentimage_index = imageNumber - 1;
|
||||
m_parseState = ParseJpegXLSuccess;
|
||||
return true;
|
||||
}
|
||||
@@ -1834,7 +1833,7 @@ int QJpegXLHandler::loopCount() const
|
||||
|
||||
bool QJpegXLHandler::rewind()
|
||||
{
|
||||
m_currentimage_index = 0;
|
||||
m_currentimage_index = -1;
|
||||
|
||||
JxlDecoderReleaseInput(m_decoder);
|
||||
JxlDecoderRewind(m_decoder);
|
||||
|
||||
@@ -65,7 +65,6 @@ private:
|
||||
ParseJpegXLState m_parseState;
|
||||
int m_quality;
|
||||
int m_currentimage_index;
|
||||
int m_previousimage_index;
|
||||
QImageIOHandler::Transformations m_transformations;
|
||||
|
||||
QByteArray m_rawData;
|
||||
|
||||
@@ -314,6 +314,8 @@ bool SGIImagePrivate::readImage(QImage &img)
|
||||
_numrows = _ysize * _zsize;
|
||||
|
||||
if (_rle) {
|
||||
// start table holds file offsets; raster data follows header + both tables
|
||||
const quint32 dataOff = 512 + _numrows * 2 * sizeof(quint32);
|
||||
uint l;
|
||||
_starttab = new (std::nothrow) quint32[_numrows];
|
||||
if (_starttab == nullptr) {
|
||||
@@ -321,7 +323,10 @@ bool SGIImagePrivate::readImage(QImage &img)
|
||||
}
|
||||
for (l = 0; !_stream.atEnd() && l < _numrows; l++) {
|
||||
_stream >> _starttab[l];
|
||||
_starttab[l] -= 512 + _numrows * 2 * sizeof(quint32);
|
||||
if (_starttab[l] < dataOff) {
|
||||
return false;
|
||||
}
|
||||
_starttab[l] -= dataOff;
|
||||
if (_stream.status() != QDataStream::Ok) {
|
||||
return false;
|
||||
}
|
||||
@@ -353,9 +358,10 @@ bool SGIImagePrivate::readImage(QImage &img)
|
||||
|
||||
// sanity check
|
||||
if (_rle) {
|
||||
const uint dataSize = uint(_data.size());
|
||||
for (uint o = 0; o < _numrows; o++) {
|
||||
// don't change to greater-or-equal!
|
||||
if (_starttab[o] + _lengthtab[o] > (uint)_data.size()) {
|
||||
// don't add start+length: uint32 wrap would pass a corrupt file
|
||||
if (_starttab[o] > dataSize || _lengthtab[o] > dataSize - _starttab[o]) {
|
||||
// qCDebug(LOG_RGBPLUGIN) << "image corrupt (sanity check failed)";
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -165,20 +165,27 @@ inline bool checkImageSize(const QSize& size, qint32 bytesPerPixel)
|
||||
return checkImageSize(size.width(), size.height(), bytesPerPixel);
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief qRoundOrZero_T
|
||||
* In images, many float values can only be positive (e.g., resolution). This function calculates
|
||||
* the roundness of the passed value, returning 0 if the value is negative or invalid.
|
||||
* \return 0 when \a d is negative, NaN, Inf or std::numeric_limits<TI>::max(). Otherwise the qRound of \a d.
|
||||
*/
|
||||
template<class TI, class SF> // SF = source FP, TI = target INT
|
||||
TI qRoundOrZero_T(SF d, bool *ok = nullptr)
|
||||
{
|
||||
bool tmp = false;
|
||||
if (ok == nullptr) {
|
||||
ok = &tmp;
|
||||
}
|
||||
|
||||
// checks for undefined behavior
|
||||
if (qIsNaN(d) || qIsInf(d) || d < SF() || d > SF(std::numeric_limits<TI>::max())) {
|
||||
if (ok) {
|
||||
*ok = false;
|
||||
}
|
||||
return 0;
|
||||
if (qIsNaN(d) || qIsInf(d) || d < SF()) {
|
||||
*ok = false;
|
||||
} else {
|
||||
*ok = d < SF(std::numeric_limits<TI>::max());
|
||||
}
|
||||
if (ok) {
|
||||
*ok = true;
|
||||
}
|
||||
return qRound(d);
|
||||
return *ok ? qRound(d) : 0;
|
||||
}
|
||||
|
||||
inline qint32 qRoundOrZero(double d, bool *ok = nullptr)
|
||||
|
||||