55#include < cstring>
66
77// Squish Includes
8- #include " squish/squish.h"
8+ #include < squish/squish.h>
9+
10+ // etc2comp Includes
11+ #include < Etc/EtcImage.h>
912
1013// NOTE: The Qt image formats (QImage::Format) used here are all byte ordered based on the host system, yet squish (and DST?)
1114// expect the input data to always be RGB(A), which means these won't work on Big Endian ordered systems;
@@ -84,6 +87,7 @@ QVector<QImage> ToTexConverter::generateMipMaps(const QImage& baseImage)
8487
8588QVector<KTex::MipMapImage> ToTexConverter::convertToTargetFormat (const QVector<QImage>& images)
8689{
90+ auto pxFormat = mOptions .pixelFormat ;
8791 QVector<KTex::MipMapImage> outputImages;
8892
8993 for (const QImage& image : images)
@@ -94,21 +98,72 @@ QVector<KTex::MipMapImage> ToTexConverter::convertToTargetFormat(const QVector<Q
9498 mipMap.setWidth (image.width ());
9599 mipMap.setHeight (image.height ());
96100
97- // Uncompressed steps
98- if (mOptions .pixelFormat == KTex::Header::PixelFormat::RGBA ||
99- mOptions .pixelFormat == KTex::Header::PixelFormat::RGB)
100- {
101- mipMap.setPitch (image.bytesPerLine ());
102- mipMap.setImageDataSize (image.sizeInBytes ());
103- std::memcpy (mipMap.imageData ().data (), image.bits (), image.sizeInBytes ());
104- }
105- else // Compressed steps
101+ // Encoder specific steps
102+ switch (pxFormat) // Use variants, inheritance, or other functions for this if many more types are added
106103 {
107- int squishFlag = getSquishCompressionFlag (mOptions .pixelFormat );
108- mipMap.setPitch (squish::GetStorageRequirements (image.width (), 1 , squishFlag));
109- mipMap.setImageDataSize (squish::GetStorageRequirements (image.width (), image.height (), squishFlag));
110- squish::CompressImage (image.bits (), image.width (), image.height (), image.bytesPerLine (),
111- mipMap.imageData ().data (), squishFlag);
104+ using enum KTex::Header::PixelFormat;
105+
106+ case RGB:
107+ case RGBA:
108+ mipMap.setPitch (image.bytesPerLine ());
109+ mipMap.setImageDataSize (image.sizeInBytes ());
110+ std::memcpy (mipMap.imageData ().data (), image.bits (), image.sizeInBytes ());
111+ break ;
112+
113+ case DXT1:
114+ case DXT3:
115+ case DXT5:
116+ {
117+ int squishFlag = getSquishCompressionFlag (pxFormat);
118+ mipMap.setPitch (squish::GetStorageRequirements (image.width (), 1 , squishFlag)); // Space for one row of blocks
119+ mipMap.setImageDataSize (squish::GetStorageRequirements (image.width (), image.height (), squishFlag));
120+ squish::CompressImage (image.bits (), image.width (), image.height (), image.bytesPerLine (),
121+ mipMap.imageData ().data (), squishFlag);
122+ }
123+
124+ case ETC2EAC:
125+ {
126+ /* The pitch calculation below boils down to:
127+ * p = blocks_per_row * block_size
128+ * where
129+ * block_size varies with format
130+ * blocks_per_row = ceil(width/4)
131+ */
132+
133+ // Get texture info
134+ auto etcFormat = Etc::Image::Format::RGBA8; // Make function for this, like for squish, if more ETC formats are supported
135+
136+ // Create ETC image
137+ auto errMetric = image.isGrayscale () ? Etc::ErrorMetric::GRAY : Etc::ErrorMetric::NUMERIC; // Could try the Rec 709 option for color
138+ /* The standard allows viewing a POD struct as a sequence of bytes (e.g. auto data = reinterpret_cast<uchar*>(myStruct)),
139+ * but does not allow the other way around; however, in the case of a very simple struct of ints, almost no known compiler
140+ * inserts padding between the members, so here we're gonna try what is technically UB, casting an array to a struct, since
141+ * it generally works and this application is non-critical. This is possible because each struct member is exactly 1-byte in
142+ * size and is laid out in the correct R-G-B-A order.
143+ *
144+ * This lib has a really strange interface, as it was hacked together by someone else after its initial creation.
145+ * You make an image with uncompressed pixel data, despite the type (Etc::Image) being named like you already have
146+ * a compressed image, and then call Encode.
147+ */
148+ Etc::Image etcImage (etcFormat, (const Etc::ColorR8G8B8A8*)image.bits (), image.width (), image.height (), errMetric);
149+
150+ // Prepare mip-map
151+ mipMap.setPitch (etcImage.GetNumberOfBlockColumns () * etcImage.GetBlockSize ()); // Space for one row of blocks
152+ mipMap.setImageDataSize (etcImage.GetEncodingBitsBytes ());
153+
154+ // Encode
155+ constexpr float quality = 90 ; /* (0-100) could add flag to allow adjusting, but awkward since its just for this format, though we could just
156+ * say "for formats where it applies" and for now it's only this one. Kram uses 49 by default and states that
157+ * unity uses "80".
158+ */
159+ auto status = etcImage.EncodeSinglepass (quality, reinterpret_cast <uchar*>(mipMap.imageData ().data ()));
160+ if (status != Etc::Image::SUCCESS)
161+ qWarning (" Unexpected ETC2 encode error: 0x%x" , status);
162+ break ;
163+ }
164+
165+ default :
166+ qCritical (" Unhandled encoding pixel format!" );
112167 }
113168
114169 outputImages.append (mipMap);
@@ -166,31 +221,63 @@ const KTex::MipMapImage& FromTexConverter::getMainImage() { return mSourceTex.mi
166221
167222QImage FromTexConverter::convertToStandardFormat (const KTex::MipMapImage& mainImage)
168223{
169- QByteArray rawData;
170- quint16 pitch;
171- QImage::Format rawFormat = mSourceTex .header ().pixelFormat () == KTex::Header::PixelFormat::RGB ? QImage::Format_RGB888 :
172- mOptions .demultiplyAlpha ? QImage::Format_RGBA8888_Premultiplied : QImage::Format_RGBA8888;
173-
174- // Uncompressed steps
175- if (mSourceTex .header ().pixelFormat () == KTex::Header::PixelFormat::RGBA ||
176- mSourceTex .header ().pixelFormat () == KTex::Header::PixelFormat::RGB)
177- {
178- pitch = mainImage.pitch ();
179- rawData = mainImage.imageData (); // Implicit sharing avoids copy
180- }
181- else // Compressed steps
224+ QByteArray decodedData;
225+ quint16 decodedPitch{};
226+ QImage::Format decodedFormat{};
227+
228+ auto pxFormat = mSourceTex .header ().pixelFormat ();
229+ switch (pxFormat) // Use variants, inheritance, or other functions for this if many more types are added
182230 {
183- // Always outputs in RGBA
184- int squishFlag = getSquishCompressionFlag (mSourceTex .header ().pixelFormat ());
185- pitch = mainImage.width () * 4 ;
186- rawData.resize (mainImage.width () * mainImage.height () * 4 );
187- squish::DecompressImage (reinterpret_cast <uchar*>(rawData.data ()), mainImage.width (), mainImage.height (), pitch,
188- mainImage.imageData ().data (), squishFlag);
231+ using enum KTex::Header::PixelFormat;
232+
233+ case RGB:
234+ decodedFormat = QImage::Format_RGB888;
235+ decodedPitch = mainImage.pitch ();
236+ decodedData = mainImage.imageData (); // Implicit sharing avoids copy
237+ break ;
238+
239+ case RGBA:
240+ decodedFormat = mOptions .demultiplyAlpha ? QImage::Format_RGBA8888_Premultiplied : QImage::Format_RGBA8888;
241+ decodedPitch = mainImage.pitch ();
242+ decodedData = mainImage.imageData (); // Implicit sharing avoids copy
243+ break ;
244+
245+ case DXT1:
246+ case DXT3:
247+ case DXT5:
248+ {
249+ decodedFormat = mOptions .demultiplyAlpha ? QImage::Format_RGBA8888_Premultiplied : QImage::Format_RGBA8888;
250+ decodedPitch = mainImage.width () * 4 ;
251+ int squishFlag = getSquishCompressionFlag (pxFormat);
252+ decodedData.resize (mainImage.width () * mainImage.height () * 4 );
253+ squish::DecompressImage (reinterpret_cast <uchar*>(decodedData.data ()), mainImage.width (), mainImage.height (), decodedPitch,
254+ mainImage.imageData ().data (), squishFlag);
255+ break ;
256+ }
257+
258+ case ETC2EAC:
259+ {
260+ decodedFormat = mOptions .demultiplyAlpha ? QImage::Format_RGBA8888_Premultiplied : QImage::Format_RGBA8888;
261+ decodedPitch = mainImage.width () * 4 ;
262+ auto etcFormat = Etc::Image::Format::RGBA8; // Make function for this, like for squish, if more ETC formats are supported
263+ decodedData.resize (mainImage.width () * mainImage.height () * 4 );
264+ /* This lib has a really strange interface, as it was hacked together by someone else after its initial creation.
265+ * You make an image with no pixel data set and then pass the pixel data as part of the Encode call
266+ */
267+ Etc::Image etcImage (etcFormat, nullptr , 1024 , 1024 , Etc::ErrorMetric::NUMERIC);
268+ auto status = etcImage.Decode (reinterpret_cast <const uchar*>(mainImage.imageData ().data ()),
269+ reinterpret_cast <uchar*>(decodedData.data ()));
270+ if (status != Etc::Image::SUCCESS)
271+ qWarning (" Unexpected ETC2 decode error: 0x%x" , status);
272+ break ;
273+ }
189274
275+ default :
276+ qCritical (" Unhandled decoding pixel format!" );
190277 }
191278
192279 // Create QImage from buffer
193- QImage bufferedImage = QImage (reinterpret_cast <uchar*>(rawData .data ()), mainImage.width (), mainImage.height (), pitch, rawFormat );
280+ QImage bufferedImage = QImage (reinterpret_cast <uchar*>(decodedData .data ()), mainImage.width (), mainImage.height (), decodedPitch, decodedFormat );
194281
195282 // Copy and detach image so it isn't reliant on buffer that will be destroyed
196283 QImage standaloneImage = bufferedImage.copy ();
0 commit comments