blob: c2606321c2516c74d543dbc32afcc285e89398b5 [file] [log] [blame]
krajcevski99ffe242014-06-03 13:04:35 -07001/*
2 * Copyright 2014 Google Inc.
3 *
4 * Use of this source code is governed by a BSD-style license that can be
5 * found in the LICENSE file.
6 */
7
8#include "SkColorPriv.h"
9#include "SkImageDecoder.h"
krajcevskic250d2e2014-06-06 06:16:28 -070010#include "SkPixelRef.h"
krajcevski99ffe242014-06-03 13:04:35 -070011#include "SkScaledBitmapSampler.h"
12#include "SkStream.h"
halcanary67ec1f82014-06-27 11:36:20 -070013#include "SkStreamPriv.h"
krajcevski99ffe242014-06-03 13:04:35 -070014#include "SkTypes.h"
15
16#include "ktx.h"
17#include "etc1.h"
18
19/////////////////////////////////////////////////////////////////////////////////////////
20
21
22/////////////////////////////////////////////////////////////////////////////////////////
23
24// KTX Image decoder
25// ---
26// KTX is a general texture data storage file format ratified by the Khronos Group. As an
27// overview, a KTX file contains all of the appropriate values needed to fully specify a
28// texture in an OpenGL application, including the use of compressed data.
29//
30// This decoder is meant to be used with an SkDiscardablePixelRef so that GPU backends
31// can sniff the data before creating a texture. If they encounter a compressed format
32// that they understand, they can then upload the data directly to the GPU. Otherwise,
33// they will decode the data into a format that Skia supports.
34
35class SkKTXImageDecoder : public SkImageDecoder {
36public:
37 SkKTXImageDecoder() { }
38
39 virtual Format getFormat() const SK_OVERRIDE {
40 return kKTX_Format;
41 }
42
43protected:
44 virtual bool onDecode(SkStream* stream, SkBitmap* bm, Mode) SK_OVERRIDE;
45
46private:
47 typedef SkImageDecoder INHERITED;
48};
49
50bool SkKTXImageDecoder::onDecode(SkStream* stream, SkBitmap* bm, Mode mode) {
51 // TODO: Implement SkStream::copyToData() that's cheap for memory and file streams
halcanary67ec1f82014-06-27 11:36:20 -070052 SkAutoDataUnref data(SkCopyStreamToData(stream));
krajcevski99ffe242014-06-03 13:04:35 -070053 if (NULL == data) {
54 return false;
55 }
56
57 SkKTXFile ktxFile(data);
58 if (!ktxFile.valid()) {
59 return false;
60 }
61
62 const unsigned short width = ktxFile.width();
63 const unsigned short height = ktxFile.height();
64
reed5926b862014-06-11 10:33:13 -070065#ifdef SK_SUPPORT_LEGACY_IMAGEDECODER_CHOOSER
krajcevski99ffe242014-06-03 13:04:35 -070066 // should we allow the Chooser (if present) to pick a config for us???
reed6c225732014-06-09 19:52:07 -070067 if (!this->chooseFromOneChoice(kN32_SkColorType, width, height)) {
krajcevski99ffe242014-06-03 13:04:35 -070068 return false;
69 }
reed5926b862014-06-11 10:33:13 -070070#endif
krajcevski99ffe242014-06-03 13:04:35 -070071
krajcevski3b570c62014-06-10 09:17:48 -070072 // Set a flag if our source is premultiplied alpha
73 const SkString premulKey("KTXPremultipliedAlpha");
74 const bool bSrcIsPremul = ktxFile.getValueForKey(premulKey) == SkString("True");
75
krajcevski99ffe242014-06-03 13:04:35 -070076 // Setup the sampler...
77 SkScaledBitmapSampler sampler(width, height, this->getSampleSize());
78
krajcevski3b570c62014-06-10 09:17:48 -070079 // Determine the alpha of the bitmap...
80 SkAlphaType alphaType = kOpaque_SkAlphaType;
81 if (ktxFile.isRGBA8()) {
82 if (this->getRequireUnpremultipliedColors()) {
83 alphaType = kUnpremul_SkAlphaType;
84 // If the client wants unpremul colors and we only have
85 // premul, then we cannot honor their wish.
86 if (bSrcIsPremul) {
87 return false;
88 }
89 } else {
90 alphaType = kPremul_SkAlphaType;
91 }
92 }
93
krajcevski99ffe242014-06-03 13:04:35 -070094 // Set the config...
krajcevski3b570c62014-06-10 09:17:48 -070095 bm->setInfo(SkImageInfo::MakeN32(sampler.scaledWidth(), sampler.scaledHeight(), alphaType));
krajcevski99ffe242014-06-03 13:04:35 -070096 if (SkImageDecoder::kDecodeBounds_Mode == mode) {
97 return true;
98 }
99
100 // If we've made it this far, then we know how to grok the data.
101 if (!this->allocPixelRef(bm, NULL)) {
102 return false;
103 }
104
105 // Lock the pixels, since we're about to write to them...
106 SkAutoLockPixels alp(*bm);
107
108 if (ktxFile.isETC1()) {
109 if (!sampler.begin(bm, SkScaledBitmapSampler::kRGB, *this)) {
110 return false;
111 }
112
113 // ETC1 Data is encoded as RGB pixels, so we should extract it as such
114 int nPixels = width * height;
115 SkAutoMalloc outRGBData(nPixels * 3);
116 etc1_byte *outRGBDataPtr = reinterpret_cast<etc1_byte *>(outRGBData.get());
117
118 // Decode ETC1
119 const etc1_byte *buf = reinterpret_cast<const etc1_byte *>(ktxFile.pixelData());
120 if (etc1_decode_image(buf, outRGBDataPtr, width, height, 3, width*3)) {
121 return false;
122 }
123
124 // Set each of the pixels...
125 const int srcRowBytes = width * 3;
126 const int dstHeight = sampler.scaledHeight();
127 const uint8_t *srcRow = reinterpret_cast<uint8_t *>(outRGBDataPtr);
128 srcRow += sampler.srcY0() * srcRowBytes;
129 for (int y = 0; y < dstHeight; ++y) {
130 sampler.next(srcRow);
131 srcRow += sampler.srcDY() * srcRowBytes;
132 }
133
134 return true;
135
136 } else if (ktxFile.isRGB8()) {
137
138 // Uncompressed RGB data (without alpha)
139 if (!sampler.begin(bm, SkScaledBitmapSampler::kRGB, *this)) {
140 return false;
141 }
142
143 // Just need to read RGB pixels
144 const int srcRowBytes = width * 3;
145 const int dstHeight = sampler.scaledHeight();
146 const uint8_t *srcRow = reinterpret_cast<const uint8_t *>(ktxFile.pixelData());
147 srcRow += sampler.srcY0() * srcRowBytes;
148 for (int y = 0; y < dstHeight; ++y) {
149 sampler.next(srcRow);
150 srcRow += sampler.srcDY() * srcRowBytes;
151 }
152
153 return true;
154
155 } else if (ktxFile.isRGBA8()) {
156
157 // Uncompressed RGBA data
krajcevski3b570c62014-06-10 09:17:48 -0700158
159 // If we know that the image contains premultiplied alpha, then
160 // we need to turn off the premultiplier
161 SkScaledBitmapSampler::Options opts (*this);
162 if (bSrcIsPremul) {
163 SkASSERT(bm->alphaType() == kPremul_SkAlphaType);
164 SkASSERT(!this->getRequireUnpremultipliedColors());
165
166 opts.fPremultiplyAlpha = false;
167 }
168
169 if (!sampler.begin(bm, SkScaledBitmapSampler::kRGBA, opts)) {
krajcevski99ffe242014-06-03 13:04:35 -0700170 return false;
171 }
172
173 // Just need to read RGBA pixels
174 const int srcRowBytes = width * 4;
175 const int dstHeight = sampler.scaledHeight();
176 const uint8_t *srcRow = reinterpret_cast<const uint8_t *>(ktxFile.pixelData());
177 srcRow += sampler.srcY0() * srcRowBytes;
178 for (int y = 0; y < dstHeight; ++y) {
179 sampler.next(srcRow);
180 srcRow += sampler.srcDY() * srcRowBytes;
181 }
182
183 return true;
184 }
185
186 return false;
187}
188
krajcevskic250d2e2014-06-06 06:16:28 -0700189///////////////////////////////////////////////////////////////////////////////
190
191// KTX Image Encoder
192//
193// This encoder takes a best guess at how to encode the bitmap passed to it. If
194// there is an installed discardable pixel ref with existing PKM data, then we
195// will repurpose the existing ETC1 data into a KTX file. If the data contains
196// KTX data, then we simply return a copy of the same data. For all other files,
197// the underlying KTX library tries to do its best to encode the appropriate
198// data specified by the bitmap based on the config. (i.e. kAlpha8_Config will
199// be represented as a full resolution 8-bit image dump with the appropriate
200// OpenGL defines in the header).
201
202class SkKTXImageEncoder : public SkImageEncoder {
203protected:
204 virtual bool onEncode(SkWStream* stream, const SkBitmap& bm, int quality) SK_OVERRIDE;
205
206private:
207 virtual bool encodePKM(SkWStream* stream, const SkData *data);
208 typedef SkImageEncoder INHERITED;
209};
210
211bool SkKTXImageEncoder::onEncode(SkWStream* stream, const SkBitmap& bitmap, int) {
212 SkAutoDataUnref data(bitmap.pixelRef()->refEncodedData());
213
214 // Is this even encoded data?
215 if (NULL != data) {
216 const uint8_t *bytes = data->bytes();
217 if (etc1_pkm_is_valid(bytes)) {
218 return this->encodePKM(stream, data);
219 }
220
221 // Is it a KTX file??
222 if (SkKTXFile::is_ktx(bytes)) {
223 return stream->write(bytes, data->size());
224 }
225
226 // If it's neither a KTX nor a PKM, then we need to
227 // get at the actual pixels, so fall through and decompress...
228 }
229
230 return SkKTXFile::WriteBitmapToKTX(stream, bitmap);
231}
232
233bool SkKTXImageEncoder::encodePKM(SkWStream* stream, const SkData *data) {
234 const uint8_t* bytes = data->bytes();
235 SkASSERT(etc1_pkm_is_valid(bytes));
236
237 etc1_uint32 width = etc1_pkm_get_width(bytes);
238 etc1_uint32 height = etc1_pkm_get_height(bytes);
239
240 // ETC1 Data is stored as compressed 4x4 pixel blocks, so we must make sure
241 // that our dimensions are valid.
242 if (width == 0 || (width & 3) != 0 || height == 0 || (height & 3) != 0) {
243 return false;
244 }
245
246 // Advance pointer to etc1 data.
247 bytes += ETC_PKM_HEADER_SIZE;
248
249 return SkKTXFile::WriteETC1ToKTX(stream, bytes, width, height);
250}
251
krajcevski99ffe242014-06-03 13:04:35 -0700252/////////////////////////////////////////////////////////////////////////////////////////
253DEFINE_DECODER_CREATOR(KTXImageDecoder);
krajcevskic250d2e2014-06-06 06:16:28 -0700254DEFINE_ENCODER_CREATOR(KTXImageEncoder);
krajcevski99ffe242014-06-03 13:04:35 -0700255/////////////////////////////////////////////////////////////////////////////////////////
256
257static SkImageDecoder* sk_libktx_dfactory(SkStreamRewindable* stream) {
258 if (SkKTXFile::is_ktx(stream)) {
259 return SkNEW(SkKTXImageDecoder);
260 }
261 return NULL;
262}
263
krajcevski99ffe242014-06-03 13:04:35 -0700264static SkImageDecoder::Format get_format_ktx(SkStreamRewindable* stream) {
265 if (SkKTXFile::is_ktx(stream)) {
266 return SkImageDecoder::kKTX_Format;
267 }
268 return SkImageDecoder::kUnknown_Format;
269}
270
krajcevskic250d2e2014-06-06 06:16:28 -0700271SkImageEncoder* sk_libktx_efactory(SkImageEncoder::Type t) {
272 return (SkImageEncoder::kKTX_Type == t) ? SkNEW(SkKTXImageEncoder) : NULL;
273}
274
275static SkImageDecoder_DecodeReg gReg(sk_libktx_dfactory);
krajcevski99ffe242014-06-03 13:04:35 -0700276static SkImageDecoder_FormatReg gFormatReg(get_format_ktx);
krajcevskic250d2e2014-06-06 06:16:28 -0700277static SkImageEncoder_EncodeReg gEReg(sk_libktx_efactory);