Krita Source Code Documentation
Loading...
Searching...
No Matches
KisPNGConverter Class Reference

#include <kis_png_converter.h>

+ Inheritance diagram for KisPNGConverter:

Public Slots

virtual void cancel ()
 

Public Member Functions

KisImportExportErrorCode buildFile (const QString &filename, const QRect &imageRect, const qreal xRes, const qreal yRes, KisPaintDeviceSP device, vKisAnnotationSP_it annotationsStart, vKisAnnotationSP_it annotationsEnd, KisPNGOptions options, KisMetaData::Store *metaData)
 
KisImportExportErrorCode buildFile (QIODevice *, const QRect &imageRect, const qreal xRes, const qreal yRes, KisPaintDeviceSP device, vKisAnnotationSP_it annotationsStart, vKisAnnotationSP_it annotationsEnd, KisPNGOptions options, KisMetaData::Store *metaData)
 
KisImportExportErrorCode buildImage (const QString &filename)
 
KisImportExportErrorCode buildImage (QIODevice *iod)
 
KisImageSP image ()
 
 KisPNGConverter (KisDocument *doc, bool batchMode=false)
 
 ~KisPNGConverter () override
 

Static Public Member Functions

static bool isColorSpaceSupported (const KoColorSpace *cs)
 
static bool saveDeviceToStore (const QString &filename, const QRect &imageRect, const qreal xRes, const qreal yRes, KisPaintDeviceSP dev, KoStore *store, KisMetaData::Store *metaData=0)
 saveDeviceToStore saves the given paint device to the KoStore. If the device is not 8 bits sRGB, it will be converted to 8 bits sRGB.
 

Private Member Functions

void progress (png_structp png_ptr, png_uint_32 row_number, int pass)
 

Private Attributes

bool m_batchMode
 
KisDocumentm_doc
 
KisImageSP m_image
 
png_uint_32 m_max_row
 
QString m_path
 
bool m_stop
 

Detailed Description

This class allows to import/export a PNG from either a file or a QIODevice.

Definition at line 77 of file kis_png_converter.h.

Constructor & Destructor Documentation

◆ KisPNGConverter()

KisPNGConverter::KisPNGConverter ( KisDocument * doc,
bool batchMode = false )

Initialize the converter.

Parameters
docthe KisDocument related to the image, can be null if you don't have a KisDocument
batchModewhether to use the batch mode

Definition at line 274 of file kis_png_converter.cpp.

275{
276 // Q_ASSERT(doc);
277 // Q_ASSERT(adapter);
278
279 m_doc = doc;
280 m_stop = false;
281 m_max_row = 0;
282 m_image = 0;
283 m_batchMode = batchMode;
284}
KisDocument * m_doc

References m_batchMode, m_doc, m_image, m_max_row, and m_stop.

◆ ~KisPNGConverter()

KisPNGConverter::~KisPNGConverter ( )
override

Definition at line 286 of file kis_png_converter.cpp.

287{
288}

Member Function Documentation

◆ buildFile() [1/2]

KisImportExportErrorCode KisPNGConverter::buildFile ( const QString & filename,
const QRect & imageRect,
const qreal xRes,
const qreal yRes,
KisPaintDeviceSP device,
vKisAnnotationSP_it annotationsStart,
vKisAnnotationSP_it annotationsEnd,
KisPNGOptions options,
KisMetaData::Store * metaData )

Save a layer to a PNG

Parameters
filenamethe name of the destination file
imageRectthe image rectangle to save
xResresolution along x axis
yResresolution along y axis
devicethe paint device to save
annotationsStartan iterator on the first annotation
annotationsEndan iterator on the last annotation
optionsPNG formatting options
metaDataimage metadata

Definition at line 1025 of file kis_png_converter.cpp.

1026{
1027 dbgFile << "Start writing PNG File " << filename;
1028 // Open a QIODevice for writing
1029 QFile fp (filename);
1030 if (!fp.open(QIODevice::WriteOnly)) {
1031 dbgFile << "Failed to open PNG File for writing";
1032 return (KisImportExportErrorCannotWrite(fp.error()));
1033 }
1034
1035 KisImportExportErrorCode result = buildFile(&fp, imageRect, xRes, yRes, device, annotationsStart, annotationsEnd, options, metaData);
1036
1037 return result;
1038}
KisImportExportErrorCode buildFile(const QString &filename, const QRect &imageRect, const qreal xRes, const qreal yRes, KisPaintDeviceSP device, vKisAnnotationSP_it annotationsStart, vKisAnnotationSP_it annotationsEnd, KisPNGOptions options, KisMetaData::Store *metaData)
#define dbgFile
Definition kis_debug.h:56

References buildFile(), and dbgFile.

◆ buildFile() [2/2]

KisImportExportErrorCode KisPNGConverter::buildFile ( QIODevice * iodevice,
const QRect & imageRect,
const qreal xRes,
const qreal yRes,
KisPaintDeviceSP device,
vKisAnnotationSP_it annotationsStart,
vKisAnnotationSP_it annotationsEnd,
KisPNGOptions options,
KisMetaData::Store * metaData )

TODO: Firefox still opens the image incorrectly if there is gAMA+cHRM tags present. According to the standard it should use iCCP tag with higher priority, but it doesn't:

"When the iCCP chunk is present, PNG decoders that recognize it and are capable of color management [ICC] shall ignore the gAMA and cHRM chunks and use the iCCP chunk instead and interpret it according to [ICC-1] and [ICC-1A]"

Definition at line 1040 of file kis_png_converter.cpp.

1041{
1043
1044 if (!options.alpha) {
1046 KoColor c(options.transparencyFillColor, device->colorSpace());
1047 tmp->fill(imageRect, c);
1048 KisPainter gc(tmp);
1049 gc.bitBlt(imageRect.topLeft(), device, imageRect);
1050 gc.end();
1051 device = tmp;
1052 }
1053
1055 options.forceSRGB = false;
1056 }
1057
1059 QString dstModel = device->colorSpace()->colorModelId().id();
1060 QString dstDepth = device->colorSpace()->colorDepthId().id();
1061 bool isFloatingPoint = device->colorSpace()->colorDepthId() == Float16BitsColorDepthID
1064 const KoColorProfile *dstProfile = device->colorSpace()->profile();
1065 bool needColorTransform = false;
1066
1067 dbgFile << "Converting to... sRgb" << options.forceSRGB << "rec2020" << options.convertFloatToRec2020 << "policy" << int(options.floatingPointConversion);
1068 if ((options.convertFloatToRec2020 & isFloatingPoint) || options.forceSRGB || !colormodels.contains(device->colorSpace()->colorModelId().id())) {
1069 dstModel = RGBAColorModelID.id();
1071
1072 needColorTransform = true;
1073
1074 if (options.convertFloatToRec2020 && isFloatingPoint) {
1077 } else {
1079 }
1080 }
1081 }
1082 // If the profile is a pq profile, but not one with 203 reference white, use the default rec2100 203 profile.
1083 // Once png can export diffuse white, we won't need this anymore.
1085 && !qFuzzyCompare(dstProfile->hdrReferenceWhite().value_or(203.0), 203.0)) {
1087 }
1088
1089 // We want to downsample when...
1090 // Option.downsample is on and we're not fp (because we manually handle fp 8bit)
1091 // When model is not rgba (no cicp writing, so no hdr, sadly).
1092 // When we're fp but the tf is not linear (fp+linear is also a situation we handle ourselves!)
1093 if (dstModel != RGBAColorModelID.id()
1094 || (!isFloatingPoint && options.downsample)
1095 || (isFloatingPoint && dstProfile->getTransferCharacteristics() != TRC_LINEAR)) {
1096 dstDepth = Integer16BitsColorDepthID.id();
1097
1098 needColorTransform = true;
1099
1100 if (options.downsample) {
1101 dstDepth = Integer8BitsColorDepthID.id();
1102 }
1103 }
1104
1105 if (needColorTransform) {
1106 const KoColorSpace *dstCs = KoColorSpaceRegistry::instance()->colorSpace(dstModel, dstDepth, dstProfile);
1107
1108 if (!dstCs) {
1110 }
1111
1112 device = new KisPaintDevice(*device);
1113 device->convertTo(dstCs);
1114 isFloatingPoint = device->colorSpace()->colorDepthId() == Float16BitsColorDepthID
1117 }
1118
1120 options.tryToSaveAsIndexed = false;
1121 }
1122
1123 // Initialize structures
1124 png_structp png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, 0, 0, 0);
1125 if (!png_ptr) {
1127 }
1128
1129#ifdef PNG_SET_USER_LIMITS_SUPPORTED
1130 /* Remove the user limits, if any */
1131 png_set_user_limits(png_ptr, 0x7fffffff, 0x7fffffff);
1132 png_set_chunk_cache_max(png_ptr, 0);
1133 png_set_chunk_malloc_max(png_ptr, 0);
1134#endif
1135
1136 png_set_error_fn(png_ptr, nullptr, nullptr, kis_png_warning);
1137 #ifdef PNG_BENIGN_ERRORS_SUPPORTED
1138 png_set_benign_errors(png_ptr, 1);
1139 #endif
1140
1141#if defined(PNG_SKIP_sRGB_CHECK_PROFILE) && defined(PNG_SET_OPTION_SUPPORTED)
1142 png_set_option(png_ptr, PNG_SKIP_sRGB_CHECK_PROFILE, PNG_OPTION_ON);
1143#endif
1144
1145
1146#ifdef PNG_READ_CHECK_FOR_INVALID_INDEX_SUPPORTED
1147 png_set_check_for_invalid_index(png_ptr, 0);
1148#endif
1149
1150 png_infop info_ptr = png_create_info_struct(png_ptr);
1151 if (!info_ptr) {
1152 png_destroy_write_struct(&png_ptr, (png_infopp)0);
1154 }
1155
1156 // If an error occurs during writing, libpng will jump here
1157 if (setjmp(png_jmpbuf(png_ptr))) {
1158 png_destroy_write_struct(&png_ptr, &info_ptr);
1160 }
1161 // Initialize the writing
1162 // png_init_io(png_ptr, fp);
1163 // Setup the progress function
1164 // XXX: Implement progress updating -- png_set_write_status_fn(png_ptr, progress);"
1165 // setProgressTotalSteps(100/*height*/);
1166
1167 /* set the zlib compression level */
1168 png_set_compression_level(png_ptr, options.compression);
1169
1170 png_set_write_fn(png_ptr, (void*)iodevice, _write_fn, _flush_fn);
1171
1172 /* set other zlib parameters */
1173 png_set_compression_mem_level(png_ptr, 8);
1174 png_set_compression_strategy(png_ptr, Z_DEFAULT_STRATEGY);
1175 png_set_compression_window_bits(png_ptr, 15);
1176 png_set_compression_method(png_ptr, 8);
1177 png_set_compression_buffer_size(png_ptr, 8192);
1178
1179 int color_nb_bits = isFloatingPoint? options.downsample? 8: 16: 8 * device->pixelSize() / device->channelCount();
1180 int color_type = getColorTypeforColorSpace(device->colorSpace(), options.alpha);
1181
1182 Q_ASSERT(color_type > -1);
1183
1184 // Try to compute a table of color if the colorspace is RGB8f
1185 QScopedArrayPointer<png_color> palette;
1186 int num_palette = 0;
1187 if (!options.alpha && options.tryToSaveAsIndexed && KoID(device->colorSpace()->id()) == KoID("RGBA")) { // png doesn't handle indexed images and alpha, and only have indexed for RGB8
1188 palette.reset(new png_color[255]);
1189
1190 KisSequentialIterator it(device, imageRect);
1191
1192 bool toomuchcolor = false;
1193 while (it.nextPixel()) {
1194 const quint8* c = it.oldRawData();
1195 bool findit = false;
1196 for (int i = 0; i < num_palette; i++) {
1197 if (palette[i].red == c[2] &&
1198 palette[i].green == c[1] &&
1199 palette[i].blue == c[0]) {
1200 findit = true;
1201 break;
1202 }
1203 }
1204 if (!findit) {
1205 if (num_palette == 255) {
1206 toomuchcolor = true;
1207 break;
1208 }
1209 palette[num_palette].red = c[2];
1210 palette[num_palette].green = c[1];
1211 palette[num_palette].blue = c[0];
1212 num_palette++;
1213 }
1214 }
1215
1216 if (!toomuchcolor) {
1217 dbgFile << "Found a palette of " << num_palette << " colors";
1218 color_type = PNG_COLOR_TYPE_PALETTE;
1219 if (num_palette <= 2) {
1220 color_nb_bits = 1;
1221 } else if (num_palette <= 4) {
1222 color_nb_bits = 2;
1223 } else if (num_palette <= 16) {
1224 color_nb_bits = 4;
1225 } else {
1226 color_nb_bits = 8;
1227 }
1228 } else {
1229 palette.reset();
1230 }
1231 }
1232
1233 int interlace_type = options.interlace ? PNG_INTERLACE_ADAM7 : PNG_INTERLACE_NONE;
1234
1236
1237 png_set_IHDR(png_ptr, info_ptr,
1238 imageRect.width(),
1239 imageRect.height(),
1240 color_nb_bits,
1241 color_type, interlace_type,
1242 PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT);
1243
1244 // set sRGB only if the profile is sRGB -- http://www.w3.org/TR/PNG/#11sRGB says sRGB and iCCP should not both be present
1245
1246 const bool sRGB = *device->colorSpace()->profile() == *KoColorSpaceRegistry::instance()->p709SRGBProfile();
1247 const bool colorProfilePQ = (device->colorSpace()->profile()->getTransferCharacteristics() == TRC_ITU_R_BT_2100_0_PQ
1249 /*
1250 * This automatically writes the correct gamma and chroma chunks along with the sRGB chunk, but firefox's
1251 * color management is bugged, so once you give it any incentive to start color managing an sRGB image it
1252 * will turn, for example, a nice desaturated rusty red into bright poppy red. So this is disabled for now.
1253 */
1254 if (options.storeExtraColorChunks && options.storeColorSpaceInfo && sRGB) {
1255 png_set_sRGB_gAMA_and_cHRM(png_ptr, info_ptr, PNG_sRGB_INTENT_PERCEPTUAL);
1256 }
1257
1258
1268 if (options.storeExtraColorChunks && options.storeColorSpaceInfo && !sRGB) {
1269 // https://www.w3.org/TR/PNG/#11gAMA
1270#if defined(PNG_GAMMA_SUPPORTED)
1271
1272
1273 if (colorProfilePQ) {
1274 // the values are set in accordance of HDR-PNG standard:
1275 // https://www.w3.org/TR/png-hdr-pq/
1276 png_set_gAMA_fixed(png_ptr, info_ptr, 15000);
1277 dbgFile << "gAMA" << "(Rec 2100)";
1278 } else {
1279 double gamma = device->colorSpace()->profile()->getEstimatedTRC().first();
1280 png_set_gAMA(png_ptr, info_ptr, gamma);
1281 }
1282#endif
1283
1284#if defined PNG_cHRM_SUPPORTED
1285 if (colorProfilePQ) {
1286 png_set_cHRM_fixed(png_ptr, info_ptr,
1287 31270, 32900, // white point
1288 70800, 29200, // red
1289 17000, 79700, // green
1290 13100, 4600 // blue
1291 );
1292 dbgFile << "cHRM" << "(Rec 2100)";
1293 } else {
1294 const QVector<KoColorimetryUtils::xyY> colorants = device->colorSpace()->profile()->getColorantsxyY();
1295 const KoColorimetryUtils::xyY whitePoint = device->colorSpace()->profile()->getWhitePointxyY();
1296 png_set_cHRM(png_ptr, info_ptr,
1297 whitePoint.x, whitePoint.y,
1298 colorants[0].x, colorants[0].y,
1299 colorants[1].x, colorants[1].y,
1300 colorants[2].x, colorants[2].y);
1301 }
1302#endif
1303 }
1304
1305
1306 // we should ensure we don't access non-existing palette object
1307 KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(palette || color_type != PNG_COLOR_TYPE_PALETTE, ImportExportCodes::Failure);
1308
1309 // set the palette
1310 if (color_type == PNG_COLOR_TYPE_PALETTE) {
1311 png_set_PLTE(png_ptr, info_ptr, palette.data(), num_palette);
1312 }
1313 // Save annotation
1314 vKisAnnotationSP_it it = annotationsStart;
1315 while (it != annotationsEnd) {
1316 if (!(*it) || (*it)->type().isEmpty()) {
1317 dbgFile << "Warning: empty annotation";
1318 it++;
1319 continue;
1320 }
1321
1322 dbgFile << "Trying to store annotation of type " << (*it) -> type() << " of size " << (*it) -> annotation() . size();
1323
1324 if ((*it) -> type().startsWith(QString("krita_attribute:"))) { //
1325 // Attribute
1326 // XXX: it should be possible to save krita_attributes in the \"CHUNKs\""
1327 dbgFile << "cannot save this annotation : " << (*it) -> type();
1328 } else if ((*it)->type() == "kpp_version" || (*it)->type() == "kpp_preset" ) {
1329 dbgFile << "Saving preset information " << (*it)->description();
1330 png_textp text = (png_textp) png_malloc(png_ptr, (png_uint_32) sizeof(png_text));
1331
1332 QByteArray keyData = (*it)->description().toLatin1();
1333 text[0].key = keyData.data();
1334 text[0].text = (char*)(*it)->annotation().data();
1335 text[0].text_length = (*it)->annotation().size();
1336 text[0].compression = -1;
1337
1338 png_set_text(png_ptr, info_ptr, text, 1);
1339 png_free(png_ptr, text);
1340 }
1341 it++;
1342 }
1343
1344 // Save the color profile
1345 const KoColorProfile* colorProfile = device->colorSpace()->profile();
1346 QByteArray colorProfileData = colorProfile->rawData();
1347
1348 if (options.storeColorSpaceInfo && !(sRGB && options.storeExtraColorChunks)) {
1349
1350
1351 const bool cicpPossible = (colorProfile->getColorPrimaries() != PRIMARIES_UNSPECIFIED && colorProfile->getTransferCharacteristics() != TRC_UNSPECIFIED
1352 && colorProfile->getColorPrimaries() < 256 && colorProfile->getTransferCharacteristics() < 256);
1353 dbgFile << options.writeCicpIfPossible << cicpPossible << colorProfilePQ << colorProfile->name();
1354 bool wroteCICP = false;
1355 if (options.writeCicpIfPossible && (cicpPossible || colorProfilePQ)) {
1356 png_byte primaries = colorProfile->getColorPrimaries();
1357 png_byte transfer = colorProfile->getTransferCharacteristics();
1358 if (colorProfilePQ) {
1359 transfer = TRC_ITU_R_BT_2100_0_PQ;
1361 }
1363 transfer = TRC_ITU_R_BT_2100_0_PQ;
1365 transfer = TRC_ITU_R_BT_2100_0_HLG;
1367 transfer = TRC_SMPTE_ST_428_1;
1368 }
1369 png_byte matrixCoef = 0;
1370 png_byte fullRange = 1;
1371 dbgFile << "Writing cicp" << primaries << transfer << matrixCoef << fullRange;
1372 png_set_cICP(png_ptr, info_ptr, primaries, transfer, matrixCoef, fullRange);
1373 wroteCICP = true;
1374 }
1375 if (!wroteCICP) {
1376#if PNG_LIBPNG_VER_MAJOR >= 1 && PNG_LIBPNG_VER_MINOR >= 5
1377 const char *typeString = "icc";
1378 png_set_iCCP(png_ptr, info_ptr, (png_const_charp)typeString, PNG_COMPRESSION_TYPE_BASE, (png_const_bytep)colorProfileData.constData(), colorProfileData . size());
1379#else
1380 // older version of libpng has a problem with constness on the parameters
1381 char typeStringICC[] = "icc";
1382 char *typeString = typeStringICC;
1383 png_set_iCCP(png_ptr, info_ptr, typeString, PNG_COMPRESSION_TYPE_BASE, colorProfileData.data(), colorProfileData . size());
1384#endif
1385 }
1386 }
1387#if defined(PNG_cLLI_SUPPORTED)
1388 if (colorProfilePQ && m_doc->image().toStrongRef()->relativeContentLightLevelInformation()) {
1389 const std::optional<KisRelativeContentLightLevelInformation> clli = m_doc->image().toStrongRef()->relativeContentLightLevelInformation();
1390 const double hdrRefWhite = colorProfile->hdrReferenceWhite().value_or(203.0);
1391 const double maxcll = clli->maxContentLightLevel * hdrRefWhite;
1392 const double maxfall = clli->maxFrameAverageLightLevel * hdrRefWhite;
1393 png_set_cLLI(png_ptr, info_ptr, maxcll, maxfall);
1394 }
1395#endif
1396#if defined(PNG_mDCV_SUPPORTED)
1397 if (colorProfilePQ && m_doc->image().toStrongRef()->colorVolumeInformation()) {
1398 const std::optional<KisColorVolumeInformation> cvi = m_doc->image().toStrongRef()->colorVolumeInformation();
1399 png_set_mDCV(png_ptr, info_ptr,
1400 cvi->white.x, cvi->white.y,
1401 cvi->red.x, cvi->red.y,
1402 cvi->green.x,cvi->green.y,
1403 cvi->blue.x, cvi->blue.y, cvi->maxLuminance, cvi->minLuminance);
1404 }
1405#endif
1406
1407 // save comments from the document information
1408 // warning: according to the official png spec, the keys need to be capitalized!
1409 if (m_doc) {
1410 png_text texts[4];
1411 int nbtexts = 0;
1412 KoDocumentInfo * info = m_doc->documentInfo();
1413 QString title = info->aboutInfo("title");
1414 if (!title.isEmpty() && options.storeMetaData) {
1415 fillText(texts + nbtexts, "Title", title);
1416 nbtexts++;
1417 }
1418 QString abstract = info->aboutInfo("subject");
1419 if (abstract.isEmpty()) {
1420 abstract = info->aboutInfo("abstract");
1421 }
1422 if (!abstract.isEmpty() && options.storeMetaData) {
1423 QString keywords = info->aboutInfo("keyword");
1424 if (!keywords.isEmpty()) {
1425 abstract = abstract + " keywords: " + keywords;
1426 }
1427 fillText(texts + nbtexts, "Description", abstract);
1428 nbtexts++;
1429 }
1430
1431 QString license = info->aboutInfo("license");
1432 if (!license.isEmpty() && options.storeMetaData) {
1433 fillText(texts + nbtexts, "Copyright", license);
1434 nbtexts++;
1435 }
1436
1437 QString author = info->authorInfo("creator");
1438 if (!author.isEmpty() && options.storeAuthor) {
1439 if (!info->authorContactInfo().isEmpty()) {
1440 QString contact = info->authorContactInfo().at(0);
1441 if (!contact.isEmpty()) {
1442 author = author+"("+contact+")";
1443 }
1444 }
1445 fillText(texts + nbtexts, "Author", author);
1446 nbtexts++;
1447 }
1448
1449 png_set_text(png_ptr, info_ptr, texts, nbtexts);
1450 }
1451
1452 // Save metadata following imagemagick way
1453
1454 // Save exif
1455 if (metaData && !metaData->empty()) {
1456 if (options.exif) {
1457 dbgFile << "Trying to save exif information";
1458
1460 Q_ASSERT(exifIO);
1461
1462 QBuffer buffer;
1463 exifIO->saveTo(metaData, &buffer, KisMetaData::IOBackend::JpegHeader);
1464 writeRawProfile(png_ptr, info_ptr, "exif", buffer.data());
1465 }
1466 // Save IPTC
1467 if (options.iptc) {
1468 dbgFile << "Trying to save iptc information";
1470 Q_ASSERT(iptcIO);
1471
1472 QBuffer buffer;
1473 iptcIO->saveTo(metaData, &buffer, KisMetaData::IOBackend::JpegHeader);
1474
1475 dbgFile << "IPTC information size is" << buffer.data().size();
1476 writeRawProfile(png_ptr, info_ptr, "iptc", buffer.data());
1477 }
1478 // Save XMP
1479 if (options.xmp) {
1480 dbgFile << "Trying to save XMP information";
1482 Q_ASSERT(xmpIO);
1483
1484 QBuffer buffer;
1485 xmpIO->saveTo(metaData, &buffer, KisMetaData::IOBackend::NoHeader);
1486
1487 dbgFile << "XMP information size is" << buffer.data().size();
1488 writeRawProfile(png_ptr, info_ptr, "xmp", buffer.data());
1489 }
1490 }
1491#if 0 // Unimplemented?
1492 // Save resolution
1493 int unit_type;
1494 png_uint_32 x_resolution, y_resolution;
1495#endif
1496 png_set_pHYs(png_ptr, info_ptr, CM_TO_POINT(xRes) * 100.0, CM_TO_POINT(yRes) * 100.0, PNG_RESOLUTION_METER); // It is the "invert" macro because we convert from point-per-inch to points
1497
1498 // Save the information to the file
1499 png_write_info(png_ptr, info_ptr);
1500 png_write_flush(png_ptr);
1501
1502 // swap byteorder on little endian machines.
1503#ifndef WORDS_BIGENDIAN
1504 if (color_nb_bits > 8)
1505 png_set_swap(png_ptr);
1506#endif
1507
1508 // Write the PNG
1509 // png_write_png(png_ptr, info_ptr, PNG_TRANSFORM_IDENTITY, 0);
1510
1511 struct RowPointersStruct {
1512 RowPointersStruct(const QSize &size, int pixelSize)
1513 : numRows(size.height())
1514 {
1515 rows = new png_byte*[numRows];
1516
1517 for (int i = 0; i < numRows; i++) {
1518 rows[i] = new png_byte[size.width() * pixelSize];
1519 }
1520 }
1521
1522 ~RowPointersStruct() {
1523 for (int i = 0; i < numRows; i++) {
1524 delete[] rows[i];
1525 }
1526 delete[] rows;
1527 }
1528
1529 const int numRows = 0;
1530 png_byte** rows = 0;
1531 };
1532
1533
1534 // Fill the data structure
1535 RowPointersStruct rowPointers(imageRect.size(), device->pixelSize());
1536
1537 int row = 0;
1538 for (int y = imageRect.y(); y < imageRect.y() + imageRect.height(); y++, row++) {
1539 KisHLineConstIteratorSP it = device->createHLineConstIteratorNG(imageRect.x(), y, imageRect.width());
1540
1541 switch (color_type) {
1542 case PNG_COLOR_TYPE_GRAY:
1543 case PNG_COLOR_TYPE_GRAY_ALPHA:
1544 if (color_nb_bits == 16) {
1545 quint16 *dst = reinterpret_cast<quint16 *>(rowPointers.rows[row]);
1546 do {
1547 const quint16 *d = reinterpret_cast<const quint16 *>(it->oldRawData());
1548 *(dst++) = d[0];
1549 if (options.alpha) *(dst++) = d[1];
1550 } while (it->nextPixel());
1551 } else {
1552 quint8 *dst = rowPointers.rows[row];
1553 do {
1554 const quint8 *d = it->oldRawData();
1555 *(dst++) = d[0];
1556 if (options.alpha) *(dst++) = d[1];
1557 } while (it->nextPixel());
1558 }
1559 break;
1560 case PNG_COLOR_TYPE_RGB:
1561 case PNG_COLOR_TYPE_RGB_ALPHA:
1562 if (isFloatingPoint) {
1563 if (color_nb_bits == 16) {
1564 if (device->colorSpace()->colorDepthId() == Float32BitsColorDepthID) {
1565 auto policyApplicator = ConversionPolicyApplicator<float, quint16>();
1566 quint16 *dst = reinterpret_cast<quint16 *>(rowPointers.rows[row]);
1567 do {
1568 const float *d = reinterpret_cast<const float *>(it->oldRawData());
1569 *(dst++) = policyApplicator.apply(options.floatingPointConversion, d[0]);
1570 *(dst++) = policyApplicator.apply(options.floatingPointConversion, d[1]);
1571 *(dst++) = policyApplicator.apply(options.floatingPointConversion, d[2]);
1572 if (options.alpha) *(dst++) = policyApplicator.apply(ConversionPolicy::KeepTheSame, d[3]);
1573 } while (it->nextPixel());
1574 }
1575#ifdef HAVE_OPENEXR
1576 else if (device->colorSpace()->colorDepthId() == Float16BitsColorDepthID) {
1577 auto policyApplicator = ConversionPolicyApplicator<half, quint16>();
1578 quint16 *dst = reinterpret_cast<quint16 *>(rowPointers.rows[row]);
1579 do {
1580 const half *d = reinterpret_cast<const half *>(it->oldRawData());
1581 *(dst++) = policyApplicator.apply(options.floatingPointConversion, d[0]);
1582 *(dst++) = policyApplicator.apply(options.floatingPointConversion, d[1]);
1583 *(dst++) = policyApplicator.apply(options.floatingPointConversion, d[2]);
1584 if (options.alpha) *(dst++) = policyApplicator.apply(ConversionPolicy::KeepTheSame, d[3]);
1585 } while (it->nextPixel());
1586 }
1587#endif
1588 // forcing to 8bit.
1589 } else {
1590 if (device->colorSpace()->colorDepthId() == Float32BitsColorDepthID) {
1591 auto policyApplicator = ConversionPolicyApplicator<float, quint8>();
1592 quint8 *dst = reinterpret_cast<quint8 *>(rowPointers.rows[row]);
1593 do {
1594 const float *d = reinterpret_cast<const float *>(it->oldRawData());
1595 *(dst++) = policyApplicator.apply(options.floatingPointConversion, d[0]);
1596 *(dst++) = policyApplicator.apply(options.floatingPointConversion, d[1]);
1597 *(dst++) = policyApplicator.apply(options.floatingPointConversion, d[2]);
1598 if (options.alpha) *(dst++) = policyApplicator.apply(ConversionPolicy::KeepTheSame, d[3]);
1599 } while (it->nextPixel());
1600 }
1601#ifdef HAVE_OPENEXR
1602 else if (device->colorSpace()->colorDepthId() == Float16BitsColorDepthID) {
1603 auto policyApplicator = ConversionPolicyApplicator<half, quint8>();
1604 quint8 *dst = reinterpret_cast<quint8 *>(rowPointers.rows[row]);
1605 do {
1606 const half *d = reinterpret_cast<const half *>(it->oldRawData());
1607 *(dst++) = policyApplicator.apply(options.floatingPointConversion, d[0]);
1608 *(dst++) = policyApplicator.apply(options.floatingPointConversion, d[1]);
1609 *(dst++) = policyApplicator.apply(options.floatingPointConversion, d[2]);
1610 if (options.alpha) *(dst++) = policyApplicator.apply(ConversionPolicy::KeepTheSame, d[3]);
1611 } while (it->nextPixel());
1612 }
1613#endif
1614 }
1615 } else {
1616 if (color_nb_bits == 16) {
1617 quint16 *dst = reinterpret_cast<quint16 *>(rowPointers.rows[row]);
1618 do {
1619 const quint16 *d = reinterpret_cast<const quint16 *>(it->oldRawData());
1620 *(dst++) = d[2];
1621 *(dst++) = d[1];
1622 *(dst++) = d[0];
1623 if (options.alpha) *(dst++) = d[3];
1624 } while (it->nextPixel());
1625 } else {
1626 quint8 *dst = rowPointers.rows[row];
1627 do {
1628 const quint8 *d = it->oldRawData();
1629 *(dst++) = d[2];
1630 *(dst++) = d[1];
1631 *(dst++) = d[0];
1632 if (options.alpha) *(dst++) = d[3];
1633 } while (it->nextPixel());
1634 }
1635 }
1636 break;
1637 case PNG_COLOR_TYPE_PALETTE: {
1638 quint8 *dst = rowPointers.rows[row];
1639 KisPNGWriteStream writestream(dst, color_nb_bits);
1640 do {
1641 const quint8 *d = it->oldRawData();
1642 int i;
1643 for (i = 0; i < num_palette; i++) {
1644 if (palette[i].red == d[2] &&
1645 palette[i].green == d[1] &&
1646 palette[i].blue == d[0]) {
1647 break;
1648 }
1649 }
1650 writestream.setNextValue(i);
1651 } while (it->nextPixel());
1652 }
1653 break;
1654 default:
1656 }
1657 }
1658
1659 png_write_image(png_ptr, rowPointers.rows);
1660
1661 // Writing is over
1662 png_write_end(png_ptr, info_ptr);
1663
1664 // Free memory
1665 png_destroy_write_struct(&png_ptr, &info_ptr);
1666 return ImportExportCodes::OK;
1667}
QList< QString > QStringList
const KoID Float32BitsColorDepthID("F32", ki18n("32-bit float/channel"))
const KoID Float64BitsColorDepthID("F64", ki18n("64-bit float/channel"))
const KoID GrayAColorModelID("GRAYA", ki18n("Grayscale/Alpha"))
const KoID Float16BitsColorDepthID("F16", ki18n("16-bit float/channel"))
const KoID Integer8BitsColorDepthID("U8", ki18n("8-bit integer/channel"))
const KoID Integer16BitsColorDepthID("U16", ki18n("16-bit integer/channel"))
const KoID RGBAColorModelID("RGBA", ki18n("RGB/Alpha"))
@ PRIMARIES_ITU_R_BT_2020_2_AND_2100_0
@ PRIMARIES_UNSPECIFIED
@ TRC_ITU_R_BT_2100_0_HLG
@ TRC_ITU_R_BT_2100_0_PQ
@ TRC_SMPTE_ST_428_1
constexpr qreal CM_TO_POINT(qreal cm)
Definition KoUnit.h:34
virtual const quint8 * oldRawData() const =0
virtual bool nextPixel()=0
KoDocumentInfo * documentInfo() const
KisImageSP image
std::optional< KisRelativeContentLightLevelInformation > relativeContentLightLevelInformation() const
relativeContentLightLevelInformation This returns (optionally) a KisRelativeContentLightLevelInformat...
std::optional< KisColorVolumeInformation > colorVolumeInformation() const
colorVolumeInformation
@ JpegHeader
Append Jpeg-style header.
@ NoHeader
Don't append any header.
virtual bool saveTo(const Store *store, QIODevice *ioDevice, HeaderType headerType=NoHeader) const =0
static KisMetadataBackendRegistry * instance()
quint32 pixelSize() const
quint32 channelCount() const
const KoColorSpace * colorSpace() const
void convertTo(const KoColorSpace *dstColorSpace, KoColorConversionTransformation::Intent renderingIntent=KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::ConversionFlags conversionFlags=KoColorConversionTransformation::internalConversionFlags(), KUndo2Command *parentCommand=nullptr, KoUpdater *progressUpdater=nullptr)
KisHLineConstIteratorSP createHLineConstIteratorNG(qint32 x, qint32 y, qint32 w) const
virtual KoID colorModelId() const =0
virtual KoID colorDepthId() const =0
virtual const KoColorProfile * profile() const =0
The class containing all meta information about a document.
QStringList authorContactInfo() const
authorContactInfo
QString authorInfo(const QString &info) const
QString aboutInfo(const QString &info) const
const T value(const QString &id) const
Definition KoID.h:30
QString id() const
Definition KoID.cpp:63
#define KIS_SAFE_ASSERT_RECOVER(cond)
Definition kis_assert.h:126
#define KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(cond, val)
Definition kis_assert.h:129
static void _flush_fn(png_structp png_ptr)
static void kis_png_warning(png_structp, png_const_charp message)
static void _write_fn(png_structp png_ptr, png_bytep data, png_size_t length)
vKisAnnotationSP::iterator vKisAnnotationSP_it
Definition kis_types.h:181
int size(const Forest< T > &forest)
Definition KisForest.h:1232
ConversionPolicy floatingPointConversion
QColor transparencyFillColor
virtual std::optional< double > hdrReferenceWhite() const =0
hdrReferenceWhite HDR reference white is only available for Perceptual Quantizer profiles that save t...
virtual QByteArray rawData() const
virtual KoColorimetryUtils::xyY getWhitePointxyY() const =0
virtual ColorPrimaries getColorPrimaries() const
getColorPrimaries
virtual QVector< qreal > getEstimatedTRC() const =0
virtual QVector< KoColorimetryUtils::xyY > getColorantsxyY() const =0
virtual TransferCharacteristics getTransferCharacteristics() const
getTransferCharacteristics This function should be subclassed at some point so we can get the value f...
const KoColorSpace * colorSpace(const QString &colorModelId, const QString &colorDepthId, const KoColorProfile *profile)
static KoColorSpaceRegistry * instance()
const KoColorProfile * p709SRGBProfile() const
const KoColorProfile * p2020PQProfile() const
const KoColorProfile * p2020G10Profile() const

References _flush_fn(), _write_fn(), KoDocumentInfo::aboutInfo(), KisPNGOptions::alpha, ApplyHLG, ApplyPQ, ApplySMPTE428, KoDocumentInfo::authorContactInfo(), KoDocumentInfo::authorInfo(), KisPainter::bitBlt(), KisPaintDevice::channelCount(), CM_TO_POINT(), KoColorSpace::colorDepthId(), KoColorSpace::colorModelId(), KisPaintDevice::colorSpace(), KoColorSpaceRegistry::colorSpace(), KisImage::colorVolumeInformation(), KisPNGOptions::compression, KisPNGOptions::convertFloatToRec2020, KisPaintDevice::convertTo(), KisPaintDevice::createHLineConstIteratorNG(), dbgFile, KisDocument::documentInfo(), KisPNGOptions::downsample, KisMetaData::Store::empty(), KisPainter::end(), KisPNGOptions::exif, ImportExportCodes::Failure, Float16BitsColorDepthID, Float32BitsColorDepthID, Float64BitsColorDepthID, KisPNGOptions::floatingPointConversion, KisPNGOptions::forceSRGB, ImportExportCodes::FormatColorSpaceUnsupported, KoColorProfile::getColorantsxyY(), KoColorProfile::getColorPrimaries(), KoColorProfile::getEstimatedTRC(), KoColorProfile::getTransferCharacteristics(), KoColorProfile::getWhitePointxyY(), GrayAColorModelID, KoColorProfile::hdrReferenceWhite(), KoID::id(), KoColorSpace::id, KisDocument::image, KisMetadataBackendRegistry::instance(), KoColorSpaceRegistry::instance(), Integer16BitsColorDepthID, Integer8BitsColorDepthID, KisPNGOptions::interlace, ImportExportCodes::InternalError, KisPNGOptions::iptc, KisMetaData::IOBackend::JpegHeader, KeepTheSame, kis_png_warning(), KIS_SAFE_ASSERT_RECOVER, KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE, m_doc, KoColorProfile::name, KisSequentialIteratorBase< IteratorPolicy, SourcePolicy, ProgressPolicy >::nextPixel(), KisBaseConstIteratorNG::nextPixel(), KisMetaData::IOBackend::NoHeader, ImportExportCodes::OK, KisSequentialIteratorBase< IteratorPolicy, SourcePolicy, ProgressPolicy >::oldRawData(), KisBaseConstAccessor::oldRawData(), KoColorSpaceRegistry::p2020G10Profile(), KoColorSpaceRegistry::p2020PQProfile(), KoColorSpaceRegistry::p709SRGBProfile(), KisPaintDevice::pixelSize(), PRIMARIES_ITU_R_BT_2020_2_AND_2100_0, PRIMARIES_UNSPECIFIED, KoColorSpace::profile(), KoColorProfile::rawData(), KisImage::relativeContentLightLevelInformation(), RGBAColorModelID, KisMetaData::IOBackend::saveTo(), KisPNGWriteStream::setNextValue(), KisPNGOptions::storeAuthor, KisPNGOptions::storeColorSpaceInfo, KisPNGOptions::storeExtraColorChunks, KisPNGOptions::storeMetaData, KisPNGOptions::transparencyFillColor, TRC_ITU_R_BT_2100_0_HLG, TRC_ITU_R_BT_2100_0_PQ, TRC_LINEAR, TRC_SMPTE_ST_428_1, TRC_UNSPECIFIED, KisPNGOptions::tryToSaveAsIndexed, KoGenericRegistry< T >::value(), KisPNGOptions::writeCicpIfPossible, KoColorimetryUtils::xyY::x, KisPNGOptions::xmp, and KoColorimetryUtils::xyY::y.

◆ buildImage() [1/2]

KisImportExportErrorCode KisPNGConverter::buildImage ( const QString & filename)

Load an image from an URL. If the image is not on a local drive, the image is first downloaded to a temporary location.

Parameters
filenamethe file name of the image

Definition at line 955 of file kis_png_converter.cpp.

956{
957 m_path = filename;
958
959 QFile fp(filename);
960 if (fp.exists()) {
961 if (!fp.open(QIODevice::ReadOnly)) {
962 dbgFile << "Failed to open PNG File";
964 }
965
966 return buildImage(&fp);
967 }
969
970}
KisImportExportErrorCode buildImage(const QString &filename)

References buildImage(), dbgFile, ImportExportCodes::FileFormatIncorrect, ImportExportCodes::FileNotExist, and m_path.

◆ buildImage() [2/2]

KisImportExportErrorCode KisPNGConverter::buildImage ( QIODevice * iod)

Load an image from a QIODevice.

Parameters
ioddevice to access the data

Definition at line 461 of file kis_png_converter.cpp.

462{
463 dbgFile << "Start decoding PNG File";
464
465 png_byte signature[8];
466 iod->peek((char*)signature, 8);
467
468#if PNG_LIBPNG_VER < 10400
469 if (!png_check_sig(signature, 8)) {
470#else
471 if (png_sig_cmp(signature, 0, 8) != 0) {
472#endif
473 iod->close();
475 }
476
477 // Initialize the internal structures
478 png_structp png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, 0, 0, 0);
479
480 if (!png_ptr) {
481 iod->close();
482 }
483
484#ifdef PNG_SET_USER_LIMITS_SUPPORTED
485 /* Remove the user limits, if any */
486 png_set_user_limits(png_ptr, 0x7fffffff, 0x7fffffff);
487 png_set_chunk_cache_max(png_ptr, 0);
488 png_set_chunk_malloc_max(png_ptr, 0);
489#endif
490
491 png_set_error_fn(png_ptr, nullptr, nullptr, kis_png_warning);
492 #ifdef PNG_BENIGN_ERRORS_SUPPORTED
493 png_set_benign_errors(png_ptr, 1);
494 #endif
495
496 png_infop info_ptr = png_create_info_struct(png_ptr);
497 if (!info_ptr) {
498 png_destroy_read_struct(&png_ptr, (png_infopp)0, (png_infopp)0);
499 iod->close();
501 }
502
503 png_infop end_info = png_create_info_struct(png_ptr);
504 if (!end_info) {
505 png_destroy_read_struct(&png_ptr, &info_ptr, (png_infopp)0);
506 iod->close();
508 }
509
510 // Catch errors
511 if (setjmp(png_jmpbuf(png_ptr))) {
512 png_destroy_read_struct(&png_ptr, &info_ptr, &end_info);
513 iod->close();
515 }
516
517 // Initialize the special
518 png_set_read_fn(png_ptr, iod, _read_fn);
519
520#if defined(PNG_SKIP_sRGB_CHECK_PROFILE) && defined(PNG_SET_OPTION_SUPPORTED)
521 png_set_option(png_ptr, PNG_SKIP_sRGB_CHECK_PROFILE, PNG_OPTION_ON);
522#endif
523
524 // read all PNG info up to image data
525 png_read_info(png_ptr, info_ptr);
526
527
528 if (png_get_color_type(png_ptr, info_ptr) == PNG_COLOR_TYPE_GRAY && png_get_bit_depth(png_ptr, info_ptr) < 8) {
529 png_set_expand(png_ptr);
530 }
531
532 if (png_get_color_type(png_ptr, info_ptr) == PNG_COLOR_TYPE_PALETTE && png_get_bit_depth(png_ptr, info_ptr) < 8) {
533 png_set_packing(png_ptr);
534 }
535
536
537 if (png_get_color_type(png_ptr, info_ptr) != PNG_COLOR_TYPE_PALETTE &&
538 (png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS))) {
539 png_set_expand(png_ptr);
540 }
541 png_read_update_info(png_ptr, info_ptr);
542
543 // Read information about the png
544 png_uint_32 width, height;
545 int color_nb_bits, color_type, interlace_type;
546 png_get_IHDR(png_ptr, info_ptr, &width, &height, &color_nb_bits, &color_type, &interlace_type, 0, 0);
547 dbgFile << "width = " << width << " height = " << height << " color_nb_bits = " << color_nb_bits << " color_type = " << color_type << " interlace_type = " << interlace_type << Qt::endl;
548 // swap byte order on little endian machines.
549#ifndef WORDS_BIGENDIAN
550 if (color_nb_bits > 8)
551 png_set_swap(png_ptr);
552#endif
553
554 // Determine the colorspace
555 QPair<QString, QString> csName = getColorSpaceForColorType(color_type, color_nb_bits);
556 if (csName.first.isEmpty()) {
557 png_destroy_read_struct(&png_ptr, &info_ptr, &end_info);
558 iod->close();
560 }
561 bool hasalpha = (color_type == PNG_COLOR_TYPE_RGB_ALPHA || color_type == PNG_COLOR_TYPE_GRAY_ALPHA);
562
563 // Read image profile
564 png_charp profile_name;
565#if PNG_LIBPNG_VER_MAJOR >= 1 && PNG_LIBPNG_VER_MINOR >= 5
566 png_bytep profile_data;
567#else
568 png_charp profile_data;
569#endif
570 int compression_type;
571 png_uint_32 proflen;
572
573 // Get the various optional chunks
574
575 // https://www.w3.org/TR/PNG/#11cHRM
576#if defined(PNG_cHRM_SUPPORTED)
577 double whitePointX, whitePointY;
578 double redX, redY;
579 double greenX, greenY;
580 double blueX, blueY;
581 png_get_cHRM(png_ptr,info_ptr, &whitePointX, &whitePointY, &redX, &redY, &greenX, &greenY, &blueX, &blueY);
582 dbgFile << "cHRM:" << whitePointX << whitePointY << redX << redY << greenX << greenY << blueX << blueY;
583#endif
584
585 // https://www.w3.org/TR/PNG/#11gAMA
586#if defined(PNG_GAMMA_SUPPORTED)
587 double gamma;
588 png_get_gAMA(png_ptr, info_ptr, &gamma);
589 dbgFile << "gAMA" << gamma;
590#endif
591
592 // https://www.w3.org/TR/PNG/#11sRGB
593#if defined(PNG_sRGB_SUPPORTED)
594 int sRGBIntent;
595 png_get_sRGB(png_ptr, info_ptr, &sRGBIntent);
596 dbgFile << "sRGB" << sRGBIntent;
597#endif
598
599 bool fromBlender = false;
600
601 png_text* text_ptr;
602 int num_comments;
603 png_get_text(png_ptr, info_ptr, &text_ptr, &num_comments);
604
605 for (int i = 0; i < num_comments; i++) {
606 QString key = QString(text_ptr[i].key).toLower();
607 if (key == "file") {
608 QString relatedFile = text_ptr[i].text;
609 if (relatedFile.contains(".blend", Qt::CaseInsensitive)){
610 fromBlender=true;
611 }
612 }
613 }
614
615 bool loadedImageHasLegacyHDRDummyProfile = false;
617
618 bool loadedCICP = false;
619
620 ConversionPolicy linearConversionPolicy = ConversionPolicy::KeepTheSame;
621#if defined(PNG_cICP_SUPPORTED)
622 png_byte primaries = 0;
623 png_byte transfer = 0;
624 png_byte matrix = 0;
625 png_byte fullrange = 0;
626 if (png_get_cICP(png_ptr, info_ptr, &primaries, &transfer, &matrix, &fullrange)) {
627 // TODO: in theory we could use chroma chunk here if non-zero.
628 dbgFile << "load cicp" << primaries << transfer << matrix << fullrange;
629 if (transfer == TRC_ITU_R_BT_2100_0_HLG) {
630 linearConversionPolicy = ConversionPolicy::ApplyHLG;
631 transfer = TRC_LINEAR;
632 csName.second = Float32BitsColorDepthID.id();
633 } else if (transfer == TRC_SMPTE_ST_428_1){
634 linearConversionPolicy = ConversionPolicy::ApplySMPTE428;
635 transfer = TRC_LINEAR;
636 csName.second = Float32BitsColorDepthID.id();
637 }
639
640 if (cicpProfile && cicpProfile->isSuitableForWorkspace()) {
641 profile = cicpProfile;
642 loadedCICP = true;
643 }
644 }
645#endif
646
647 if (!loadedCICP) {
648 if (png_get_iCCP(png_ptr, info_ptr, &profile_name, &compression_type, &profile_data, &proflen)) {
649 QByteArray profile_rawdata(reinterpret_cast<char*>(profile_data), proflen);
650 profile = KoColorSpaceRegistry::instance()->createColorProfile(csName.first, csName.second, profile_rawdata);
651 if (profile) {
652 if (!profile->isSuitableForWorkspace()) {
653 dbgFile << "the profile is not suitable for output and therefore cannot be used in krita, we need to convert the image to a standard profile";
654 }
655 }
656
657 loadedImageHasLegacyHDRDummyProfile = strcmp(profile_name, "ITUR_2100_PQ_FULL") == 0;
658 }
659 else if (color_nb_bits == 16 && !fromBlender && !qAppName().toLower().contains("test") && !m_batchMode) {
660 // Ask the user which color profile to use
661 KisConfig cfg(true);
662 quint32 behaviour = cfg.pasteBehaviour();
663 if (behaviour == KisClipboard::PASTE_ASK) {
664 KisDlgPngImport dlg(m_path, csName.first, csName.second);
666 Q_UNUSED(hijacker);
667 dlg.exec();
668 if (!dlg.profile().isEmpty()) {
669 profile = KoColorSpaceRegistry::instance()->profileByName(dlg.profile());
670 }
671 }
672 }
673 else {
674 dbgFile << "no embedded profile, will use the default sRGB profile";
675 }
676 }
677
678 const QString colorSpaceId =
679 KoColorSpaceRegistry::instance()->colorSpaceId(csName.first, csName.second);
680
681 // Check that the profile is used by the color space
682 if (profile
683 && (!KoColorSpaceRegistry::instance()->profileIsCompatible(profile, colorSpaceId)
684 || !(profile->isSuitableForOutput() || profile->isSuitableForInput()))) {
685 warnFile << "The profile " << profile->name() << " is not compatible with the color space model " << csName.first << " " << csName.second;
686 profile = 0;
687 }
688
689 // Retrieve a pointer to the colorspace
690 KoColorConversionTransformation* transform = 0;
691 const KoColorSpace* cs = 0;
692
693 if (loadedImageHasLegacyHDRDummyProfile &&
694 csName.first == RGBAColorModelID.id() &&
695 csName.second == Integer16BitsColorDepthID.id()) {
696
697 const KoColorSpace *p2020PQCS =
702
703 cs = p2020PQCS;
704
705 } else if (profile && profile->isSuitableForWorkspace()) {
706 dbgFile << "image has embedded profile: " << profile->name() << "\n";
707 cs = KoColorSpaceRegistry::instance()->colorSpace(csName.first, csName.second, profile);
708 }
709 else {
710 // Loading a backup colorspace
711 cs = KoColorSpaceRegistry::instance()->colorSpace(csName.first, csName.second, "");
712 }
713
714 // Create the cmsTransform if needed
715 if (profile && !profile->isSuitableForWorkspace() && profile->isSuitableForInput()) {
717 }
718
719 if (cs == 0) {
720 png_destroy_read_struct(&png_ptr, &info_ptr, &end_info);
722 }
723
724 // Creating the KisImageSP
725 if (m_image == 0) {
727 m_image = new KisImage(store, width, height, cs, "built image");
728 }
729
730 // Read resolution
731 int unit_type;
732 png_uint_32 x_resolution = 0, y_resolution = 0;
733
734 png_get_pHYs(png_ptr, info_ptr, &x_resolution, &y_resolution, &unit_type);
735 if (x_resolution > 0 && y_resolution > 0 && unit_type == PNG_RESOLUTION_METER) {
736 m_image->setResolution((double) POINT_TO_CM(x_resolution) / 100.0, (double) POINT_TO_CM(y_resolution) / 100.0); // It is the "invert" macro because we convert from point-per-inch to points
737 } else if (unit_type == PNG_RESOLUTION_UNKNOWN) {
738 m_image->setResolution(100.0, 100.0);
739 }
740
741#if defined(PNG_cLLI_SUPPORTED)
743 if (png_get_cLLI(png_ptr, info_ptr, &clli.maxContentLightLevel, &clli.maxFrameAverageLightLevel)) {
744 const double refWhiteMultiplier = 1.0/profile->hdrReferenceWhite().value_or(203.0);
745 clli.maxContentLightLevel *= refWhiteMultiplier;
746 clli.maxFrameAverageLightLevel *= refWhiteMultiplier;
748 }
749#endif
750
751#if defined(PNG_mDCV_SUPPORTED)
753 if (png_get_mDCV(png_ptr, info_ptr,
754 &cvi.white.x, &cvi.white.y,
755 &cvi.red.x, &cvi.red.y,
756 &cvi.green.x, &cvi.green.y,
757 &cvi.blue.x, &cvi.blue.y,
758 &cvi.maxLuminance, &cvi.minLuminance)) {
760 }
761#endif
762
763 double coeff = quint8_MAX / (double)(pow((double)2, color_nb_bits) - 1);
764 KisPaintLayerSP layer = new KisPaintLayer(m_image.data(), m_image -> nextLayerName(), UCHAR_MAX);
765
766 // Read comments/texts...
767 png_get_text(png_ptr, info_ptr, &text_ptr, &num_comments);
768 if (m_doc) {
770 dbgFile << "There are " << num_comments << " comments in the text";
771 for (int i = 0; i < num_comments; i++) {
772 QString key = QString(text_ptr[i].key).toLower();
773 dbgFile << "key: " << text_ptr[i].key
774 << ", containing: " << text_ptr[i].text
775 << ": " << (key == "raw profile type exif " ? "isExif" : "something else");
776 if (key == "title") {
777 info->setAboutInfo("title", text_ptr[i].text);
778 } else if (key == "description") {
779 info->setAboutInfo("comment", text_ptr[i].text);
780 } else if (key == "author") {
781 info->setAuthorInfo("creator", text_ptr[i].text);
782 } else if (key.contains("raw profile type exif")) {
783 decode_meta_data(text_ptr + i, layer->metaData(), "exif", 6);
784 } else if (key.contains("raw profile type iptc")) {
785 decode_meta_data(text_ptr + i, layer->metaData(), "iptc", 14);
786 } else if (key.contains("raw profile type xmp")) {
787 decode_meta_data(text_ptr + i, layer->metaData(), "xmp", 0);
788 } else if (key == "version") {
789 m_image->addAnnotation(new KisAnnotation("kpp_version", "version", QByteArray(text_ptr[i].text)));
790 } else if (key == "preset") {
791 m_image->addAnnotation(new KisAnnotation("kpp_preset", "preset", QByteArray(text_ptr[i].text)));
792 }
793 }
794 }
795 // Read image data
796 QScopedPointer<KisPNGReaderAbstract> reader;
797 try {
798 if (interlace_type == PNG_INTERLACE_ADAM7) {
799 reader.reset(new KisPNGReaderFullImage(png_ptr, info_ptr, width, height));
800 } else {
801 reader.reset(new KisPNGReaderLineByLine(png_ptr, info_ptr, width, height));
802 }
803 } catch (const std::bad_alloc& e) {
804 // new png_byte[] may raise such an exception if the image
805 // is invalid / to large.
806 dbgFile << "bad alloc: " << e.what();
807 // Free only the already allocated png_byte instances.
808 png_destroy_read_struct(&png_ptr, &info_ptr, &end_info);
810 }
811
812 // Read the palette if the file is indexed
813 png_colorp palette ;
814 int num_palette;
815 if (color_type == PNG_COLOR_TYPE_PALETTE) {
816 png_get_PLTE(png_ptr, info_ptr, &palette, &num_palette);
817 }
818
819 // Read the transparency palette
820 quint8 palette_alpha[256];
821 memset(palette_alpha, 255, 256);
822 if (png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS)) {
823 if (color_type == PNG_COLOR_TYPE_PALETTE) {
824 png_bytep alpha_ptr;
825 int num_alpha;
826 png_get_tRNS(png_ptr, info_ptr, &alpha_ptr, &num_alpha, 0);
827 for (int i = 0; i < num_alpha; ++i) {
828 palette_alpha[i] = alpha_ptr[i];
829 }
830 }
831 }
832
833 for (png_uint_32 y = 0; y < height; y++) {
834 KisHLineIteratorSP it = layer -> paintDevice() -> createHLineIteratorNG(0, y, width);
835
836 png_bytep row_pointer = reader->readLine();
837
838 switch (color_type) {
839 case PNG_COLOR_TYPE_GRAY:
840 case PNG_COLOR_TYPE_GRAY_ALPHA:
841 if (color_nb_bits == 16) {
842 quint16 *src = reinterpret_cast<quint16 *>(row_pointer);
843 do {
844 quint16 *d = reinterpret_cast<quint16 *>(it->rawData());
845 d[0] = *(src++);
846 if (hasalpha) {
847 d[1] = *(src++);
848 } else {
849 d[1] = quint16_MAX;
850 }
851 if (transform) transform->transformInPlace(reinterpret_cast<quint8*>(d), reinterpret_cast<quint8*>(d), 1);
852 } while (it->nextPixel());
853 } else {
854 KisPNGReadStream stream(row_pointer, color_nb_bits);
855 do {
856 quint8 *d = it->rawData();
857 d[0] = (quint8)(stream.nextValue() * coeff);
858 if (hasalpha) {
859 d[1] = (quint8)(stream.nextValue() * coeff);
860 } else {
861 d[1] = UCHAR_MAX;
862 }
863 if (transform) transform->transformInPlace(d, d, 1);
864 } while (it->nextPixel());
865 }
866 // FIXME:should be able to read 1 and 4 bits depth and scale them to 8 bits"
867 break;
868 case PNG_COLOR_TYPE_RGB:
869 case PNG_COLOR_TYPE_RGB_ALPHA:
870 if (linearConversionPolicy != ConversionPolicy::KeepTheSame) {
871 if (color_nb_bits == 16) {
873 quint16 *src = reinterpret_cast<quint16 *>(row_pointer);
874 do {
875 float *d = reinterpret_cast<float *>(it->rawData());
876 d[0] = policy.remove(linearConversionPolicy, *(src++));
877 d[1] = policy.remove(linearConversionPolicy, *(src++));
878 d[2] = policy.remove(linearConversionPolicy, *(src++));
879 if (hasalpha) d[3] = policy.remove(ConversionPolicy::KeepTheSame, *(src++));
880 else d[3] = 1.0;
881 if (transform) transform->transformInPlace(reinterpret_cast<quint8 *>(d), reinterpret_cast<quint8*>(d), 1);
882 } while (it->nextPixel());
883 } else {
885 quint8 *src = reinterpret_cast<quint8 *>(row_pointer);
886 do {
887 float *d = reinterpret_cast<float *>(it->rawData());
888 d[0] = policy.remove(linearConversionPolicy, *(src++));
889 d[1] = policy.remove(linearConversionPolicy, *(src++));
890 d[2] = policy.remove(linearConversionPolicy, *(src++));
891 if (hasalpha) d[3] = policy.remove(ConversionPolicy::KeepTheSame, *(src++));
892 else d[3] = 1.0;
893 if (transform) transform->transformInPlace(reinterpret_cast<quint8 *>(d), reinterpret_cast<quint8*>(d), 1);
894 } while (it->nextPixel());
895 }
896 } else {
897 if (color_nb_bits == 16) {
898 quint16 *src = reinterpret_cast<quint16 *>(row_pointer);
899 do {
900 quint16 *d = reinterpret_cast<quint16 *>(it->rawData());
901 d[2] = *(src++);
902 d[1] = *(src++);
903 d[0] = *(src++);
904 if (hasalpha) d[3] = *(src++);
905 else d[3] = quint16_MAX;
906 if (transform) transform->transformInPlace(reinterpret_cast<quint8 *>(d), reinterpret_cast<quint8*>(d), 1);
907 } while (it->nextPixel());
908 } else {
909 KisPNGReadStream stream(row_pointer, color_nb_bits);
910 do {
911 quint8 *d = it->rawData();
912 d[2] = (quint8)(stream.nextValue() * coeff);
913 d[1] = (quint8)(stream.nextValue() * coeff);
914 d[0] = (quint8)(stream.nextValue() * coeff);
915 if (hasalpha) d[3] = (quint8)(stream.nextValue() * coeff);
916 else d[3] = UCHAR_MAX;
917 if (transform) transform->transformInPlace(d, d, 1);
918 } while (it->nextPixel());
919 }
920 }
921 break;
922 case PNG_COLOR_TYPE_PALETTE: {
923 KisPNGReadStream stream(row_pointer, color_nb_bits);
924 do {
925 quint8 *d = it->rawData();
926 quint8 index = stream.nextValue();
927 quint8 alpha = palette_alpha[ index ];
928 if (alpha == 0) {
929 memset(d, 0, 4);
930 } else {
931 png_color c = palette[ index ];
932 d[2] = c.red;
933 d[1] = c.green;
934 d[0] = c.blue;
935 d[3] = alpha;
936 }
937 } while (it->nextPixel());
938 }
939 break;
940 default:
942 }
943 }
944 m_image->addNode(layer.data(), m_image->rootLayer().data());
945
946 png_read_end(png_ptr, end_info);
947 iod->close();
948
949 // Freeing memory
950 png_destroy_read_struct(&png_ptr, &info_ptr, &end_info);
952
953}
ColorPrimaries
The colorPrimaries enum Enum of colorants, follows ITU H.273 for values 0 to 255, and has extra known...
TransferCharacteristics
The transferCharacteristics enum Enum of transfer characteristics, follows ITU H.273 for values 0 to ...
constexpr qreal POINT_TO_CM(qreal px)
Definition KoUnit.h:33
A data extension mechanism for Krita.
The KisCursorOverrideHijacker class stores all override cursors in a stack, and resets them back afte...
KisUndoStore * createUndoStore()
void setColorVolumeInformation(const std::optional< KisColorVolumeInformation > cvi)
Set the color volume information.
void addAnnotation(KisAnnotationSP annotation)
KisGroupLayerSP rootLayer() const
void setRelativeContentLightLevelInformation(const std::optional< KisRelativeContentLightLevelInformation > clli)
void setResolution(double xres, double yres)
virtual KoColorConversionTransformation * createColorConverter(const KoColorSpace *dstColorSpace, KoColorConversionTransformation::Intent renderingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags) const
void setAboutInfo(const QString &info, const QString &data)
void setAuthorInfo(const QString &info, const QString &data)
#define warnFile
Definition kis_debug.h:99
const quint16 quint16_MAX
Definition kis_global.h:25
const quint8 quint8_MAX
Definition kis_global.h:24
static void _read_fn(png_structp png_ptr, png_bytep data, png_size_t length)
QString getColorSpaceForColorType(uint16_t sampletype, uint16_t color_type, uint16_t color_nb_bits, TIFF *image, uint16_t &nbchannels, uint16_t &extrasamplescount, uint8_t &destDepth)
The KisColorVolumeInformation class is a struct that represents the 'mastering' display....
double maxLuminance
Maximum screen brightness in cd/m²
KoColorimetryUtils::xy blue
xyY location of the blue colorant.
double minLuminance
Minimum screen brightness in cd/m²
KoColorimetryUtils::xy white
xyY location of the whitepoint.
KoColorimetryUtils::xy red
xyY location of the red colorant.
KoColorimetryUtils::xy green
xyY location of the green colorant.
KisMetaData::Store * metaData()
bool addNode(KisNodeSP node, KisNodeSP parent=KisNodeSP(), KisNodeAdditionFlags flags=KisNodeAdditionFlag::None)
double maxFrameAverageLightLevel
maxFrameAverage MaxFrameAverageLightLevel or MaxFALL is the average of average pixel brightnesses in ...
void transformInPlace(const quint8 *src, quint8 *dst, qint32 nPixels) const
The KoColorProfileQuery struct.
virtual bool isSuitableForOutput() const =0
virtual bool isSuitableForWorkspace() const =0
virtual bool isSuitableForInput() const =0
const KoColorProfile * profileByName(const QString &name) const
QString colorSpaceId(const QString &colorModelId, const QString &colorDepthId) const
const KoColorProfile * profileFor(const KoColorProfileQuery &query, const bool generate=true) const
profileFor tries to find the profile that matches these characteristics, if no such profile is found,...
const KoColorProfile * createColorProfile(const QString &colorModelId, const QString &colorDepthId, const QByteArray &rawData)

References _read_fn(), KisImage::addAnnotation(), KisNodeFacade::addNode(), ApplyHLG, ApplySMPTE428, KisColorVolumeInformation::blue, KoColorSpaceRegistry::colorSpace(), KoColorSpaceRegistry::colorSpaceId(), KoColorSpace::createColorConverter(), KoColorSpaceRegistry::createColorProfile(), KisDocument::createUndoStore(), KisSharedPtr< T >::data(), dbgFile, KisDocument::documentInfo(), ImportExportCodes::Failure, ImportExportCodes::FileFormatIncorrect, Float32BitsColorDepthID, ImportExportCodes::FormatColorSpaceUnsupported, ImportExportCodes::FormatFeaturesUnsupported, getColorSpaceForColorType(), KisColorVolumeInformation::green, KoColorProfile::hdrReferenceWhite(), KoID::id(), KoColorSpaceRegistry::instance(), Integer16BitsColorDepthID, KoColorConversionTransformation::internalConversionFlags(), KoColorConversionTransformation::internalRenderingIntent(), KoColorProfile::isSuitableForInput(), KoColorProfile::isSuitableForOutput(), KoColorProfile::isSuitableForWorkspace(), KeepTheSame, kis_png_warning(), m_batchMode, m_doc, m_image, m_path, KisRelativeContentLightLevelInformation::maxContentLightLevel, KisRelativeContentLightLevelInformation::maxFrameAverageLightLevel, KisColorVolumeInformation::maxLuminance, KisLayer::metaData(), KisColorVolumeInformation::minLuminance, KoColorProfile::name, KisPNGReadStream::nextValue(), ImportExportCodes::OK, KoColorSpaceRegistry::p2020PQProfile(), KoColorSpaceRegistry::p709SRGBProfile(), KisClipboard::PASTE_ASK, KisConfig::pasteBehaviour(), POINT_TO_CM(), KisDlgPngImport::profile(), KoColorSpaceRegistry::profileByName(), KoColorSpaceRegistry::profileFor(), quint16_MAX, quint8_MAX, KisColorVolumeInformation::red, RGBAColorModelID, KisImage::rootLayer(), KoDocumentInfo::setAboutInfo(), KoDocumentInfo::setAuthorInfo(), KisImage::setColorVolumeInformation(), KisImage::setRelativeContentLightLevelInformation(), KisImage::setResolution(), KoColorConversionTransformation::transformInPlace(), TRC_ITU_R_BT_2100_0_HLG, TRC_LINEAR, TRC_SMPTE_ST_428_1, warnFile, KisColorVolumeInformation::white, KoColorimetryUtils::xy::x, and KoColorimetryUtils::xy::y.

◆ cancel

void KisPNGConverter::cancel ( )
virtualslot

Definition at line 1670 of file kis_png_converter.cpp.

1671{
1672 m_stop = true;
1673}

References m_stop.

◆ image()

KisImageSP KisPNGConverter::image ( )

Retrieve the constructed image

Definition at line 973 of file kis_png_converter.cpp.

974{
975 return m_image;
976}

References m_image.

◆ isColorSpaceSupported()

bool KisPNGConverter::isColorSpaceSupported ( const KoColorSpace * cs)
static

Definition at line 1681 of file kis_png_converter.cpp.

1682{
1683 return colorSpaceIdSupported(cs->id());
1684}

References KoColorSpace::id.

◆ progress()

void KisPNGConverter::progress ( png_structp png_ptr,
png_uint_32 row_number,
int pass )
private

Definition at line 1675 of file kis_png_converter.cpp.

1676{
1677 if (png_ptr == 0 || row_number > PNG_MAX_UINT || pass > 7) return;
1678 // setProgress(row_number);
1679}
#define PNG_MAX_UINT

References PNG_MAX_UINT.

◆ saveDeviceToStore()

bool KisPNGConverter::saveDeviceToStore ( const QString & filename,
const QRect & imageRect,
const qreal xRes,
const qreal yRes,
KisPaintDeviceSP dev,
KoStore * store,
KisMetaData::Store * metaData = 0 )
static

saveDeviceToStore saves the given paint device to the KoStore. If the device is not 8 bits sRGB, it will be converted to 8 bits sRGB.

Returns
true if the saving succeeds

Definition at line 978 of file kis_png_converter.cpp.

979{
980 if (store->open(filename)) {
981 KoStoreDevice io(store);
982 if (!io.open(QIODevice::WriteOnly)) {
983 dbgFile << "Could not open for writing:" << filename;
984 return false;
985 }
986 KisPNGConverter pngconv(0);
987 vKisAnnotationSP_it annotIt;
988 KisMetaData::Store* metaDataStore = 0;
989 if (metaData) {
990 metaDataStore = new KisMetaData::Store(*metaData);
991 }
992 KisPNGOptions options;
993 options.compression = 3;
994 options.interlace = false;
995 options.tryToSaveAsIndexed = false;
996 options.alpha = true;
997 options.storeColorSpaceInfo = true;
998 options.downsample = false;
999
1000 if (dev->colorSpace()->id() != "RGBA") {
1001 dev = new KisPaintDevice(*dev.data());
1003 }
1004
1005 KisImportExportErrorCode success = pngconv.buildFile(&io, imageRect, xRes, yRes, dev, annotIt, annotIt, options, metaDataStore);
1006 if (!success.isOk()) {
1007 dbgFile << "Saving PNG failed:" << filename;
1008 delete metaDataStore;
1009 return false;
1010 }
1011 delete metaDataStore;
1012 io.close();
1013 if (!store->close()) {
1014 return false;
1015 }
1016 } else {
1017 dbgFile << "Opening of data file failed :" << filename;
1018 return false;
1019 }
1020 return true;
1021
1022}
bool close()
Definition KoStore.cpp:156
bool open(const QString &name)
Definition KoStore.cpp:109

References KisPNGOptions::alpha, buildFile(), KoStore::close(), KoStoreDevice::close(), KisPaintDevice::colorSpace(), KisPNGOptions::compression, KisPaintDevice::convertTo(), KisSharedPtr< T >::data(), dbgFile, KisPNGOptions::downsample, KoColorSpace::id, KoColorSpaceRegistry::instance(), KisPNGOptions::interlace, KisImportExportErrorCode::isOk(), KoStore::open(), KoStoreDevice::open(), KisPNGOptions::storeColorSpaceInfo, and KisPNGOptions::tryToSaveAsIndexed.

Member Data Documentation

◆ m_batchMode

bool KisPNGConverter::m_batchMode
private

Definition at line 136 of file kis_png_converter.h.

◆ m_doc

KisDocument* KisPNGConverter::m_doc
private

Definition at line 134 of file kis_png_converter.h.

◆ m_image

KisImageSP KisPNGConverter::m_image
private

Definition at line 133 of file kis_png_converter.h.

◆ m_max_row

png_uint_32 KisPNGConverter::m_max_row
private

Definition at line 132 of file kis_png_converter.h.

◆ m_path

QString KisPNGConverter::m_path
private

Definition at line 137 of file kis_png_converter.h.

◆ m_stop

bool KisPNGConverter::m_stop
private

Definition at line 135 of file kis_png_converter.h.


The documentation for this class was generated from the following files: