OLD | NEW |
1 /* | 1 /* |
2 * Copyright 2014 Google Inc. | 2 * Copyright 2014 Google Inc. |
3 * | 3 * |
4 * Use of this source code is governed by a BSD-style license that can be | 4 * Use of this source code is governed by a BSD-style license that can be |
5 * found in the LICENSE file. | 5 * found in the LICENSE file. |
6 */ | 6 */ |
7 | 7 |
8 #include "SkColorPriv.h" | 8 #include "SkColorPriv.h" |
9 #include "SkImageDecoder.h" | 9 #include "SkImageEncoder.h" |
10 #include "SkImageGenerator.h" | 10 #include "SkImageGenerator.h" |
11 #include "SkPixelRef.h" | 11 #include "SkPixelRef.h" |
12 #include "SkScaledBitmapSampler.h" | |
13 #include "SkStream.h" | 12 #include "SkStream.h" |
14 #include "SkStreamPriv.h" | 13 #include "SkStreamPriv.h" |
15 #include "SkTypes.h" | 14 #include "SkTypes.h" |
16 | 15 |
17 #include "ktx.h" | 16 #include "ktx.h" |
18 #include "etc1.h" | 17 #include "etc1.h" |
19 | 18 |
20 ////////////////////////////////////////////////////////////////////////////////
///////// | |
21 | |
22 | |
23 ////////////////////////////////////////////////////////////////////////////////
///////// | |
24 | |
25 // KTX Image decoder | |
26 // --- | |
27 // KTX is a general texture data storage file format ratified by the Khronos Gro
up. As an | |
28 // overview, a KTX file contains all of the appropriate values needed to fully s
pecify a | |
29 // texture in an OpenGL application, including the use of compressed data. | |
30 // | |
31 // This decoder is meant to be used with an SkDiscardablePixelRef so that GPU ba
ckends | |
32 // can sniff the data before creating a texture. If they encounter a compressed
format | |
33 // that they understand, they can then upload the data directly to the GPU. Othe
rwise, | |
34 // they will decode the data into a format that Skia supports. | |
35 | |
36 class SkKTXImageDecoder : public SkImageDecoder { | |
37 public: | |
38 SkKTXImageDecoder() { } | |
39 | |
40 Format getFormat() const override { | |
41 return kKTX_Format; | |
42 } | |
43 | |
44 protected: | |
45 Result onDecode(SkStream* stream, SkBitmap* bm, Mode) override; | |
46 | |
47 private: | |
48 typedef SkImageDecoder INHERITED; | |
49 }; | |
50 | |
51 SkImageDecoder::Result SkKTXImageDecoder::onDecode(SkStream* stream, SkBitmap* b
m, Mode mode) { | |
52 // TODO: Implement SkStream::copyToData() that's cheap for memory and file s
treams | |
53 auto data = SkCopyStreamToData(stream); | |
54 if (nullptr == data) { | |
55 return kFailure; | |
56 } | |
57 | |
58 SkKTXFile ktxFile(data.get()); | |
59 if (!ktxFile.valid()) { | |
60 return kFailure; | |
61 } | |
62 | |
63 const unsigned short width = ktxFile.width(); | |
64 const unsigned short height = ktxFile.height(); | |
65 | |
66 // Set a flag if our source is premultiplied alpha | |
67 const SkString premulKey("KTXPremultipliedAlpha"); | |
68 const bool bSrcIsPremul = ktxFile.getValueForKey(premulKey) == SkString("Tru
e"); | |
69 | |
70 // Setup the sampler... | |
71 SkScaledBitmapSampler sampler(width, height, this->getSampleSize()); | |
72 | |
73 // Determine the alpha of the bitmap... | |
74 SkAlphaType alphaType = kOpaque_SkAlphaType; | |
75 if (ktxFile.isRGBA8()) { | |
76 if (this->getRequireUnpremultipliedColors()) { | |
77 alphaType = kUnpremul_SkAlphaType; | |
78 // If the client wants unpremul colors and we only have | |
79 // premul, then we cannot honor their wish. | |
80 if (bSrcIsPremul) { | |
81 return kFailure; | |
82 } | |
83 } else { | |
84 alphaType = kPremul_SkAlphaType; | |
85 } | |
86 } | |
87 | |
88 // Search through the compressed formats to see if the KTX file is holding | |
89 // compressed data | |
90 bool ktxIsCompressed = false; | |
91 SkTextureCompressor::Format ktxCompressedFormat; | |
92 for (int i = 0; i < SkTextureCompressor::kFormatCnt; ++i) { | |
93 SkTextureCompressor::Format fmt = static_cast<SkTextureCompressor::Forma
t>(i); | |
94 if (ktxFile.isCompressedFormat(fmt)) { | |
95 ktxIsCompressed = true; | |
96 ktxCompressedFormat = fmt; | |
97 break; | |
98 } | |
99 } | |
100 | |
101 // If the compressed format is a grayscale image, then setup the bitmap prop
erly... | |
102 bool isCompressedAlpha = ktxIsCompressed && | |
103 ((SkTextureCompressor::kLATC_Format == ktxCompressedFormat) || | |
104 (SkTextureCompressor::kR11_EAC_Format == ktxCompressedFormat)); | |
105 | |
106 // Set the image dimensions and underlying pixel type. | |
107 if (isCompressedAlpha) { | |
108 const int w = sampler.scaledWidth(); | |
109 const int h = sampler.scaledHeight(); | |
110 bm->setInfo(SkImageInfo::MakeA8(w, h)); | |
111 } else { | |
112 const int w = sampler.scaledWidth(); | |
113 const int h = sampler.scaledHeight(); | |
114 bm->setInfo(SkImageInfo::MakeN32(w, h, alphaType)); | |
115 } | |
116 | |
117 if (SkImageDecoder::kDecodeBounds_Mode == mode) { | |
118 return kSuccess; | |
119 } | |
120 | |
121 // If we've made it this far, then we know how to grok the data. | |
122 if (!this->allocPixelRef(bm, nullptr)) { | |
123 return kFailure; | |
124 } | |
125 | |
126 // Lock the pixels, since we're about to write to them... | |
127 SkAutoLockPixels alp(*bm); | |
128 | |
129 if (isCompressedAlpha) { | |
130 if (!sampler.begin(bm, SkScaledBitmapSampler::kGray, *this)) { | |
131 return kFailure; | |
132 } | |
133 | |
134 // Alpha data is only a single byte per pixel. | |
135 int nPixels = width * height; | |
136 SkAutoMalloc outRGBData(nPixels); | |
137 uint8_t *outRGBDataPtr = reinterpret_cast<uint8_t *>(outRGBData.get()); | |
138 | |
139 // Decode the compressed format | |
140 const uint8_t *buf = reinterpret_cast<const uint8_t *>(ktxFile.pixelData
()); | |
141 if (!SkTextureCompressor::DecompressBufferFromFormat( | |
142 outRGBDataPtr, width, buf, width, height, ktxCompressedFormat))
{ | |
143 return kFailure; | |
144 } | |
145 | |
146 // Set each of the pixels... | |
147 const int srcRowBytes = width; | |
148 const int dstHeight = sampler.scaledHeight(); | |
149 const uint8_t *srcRow = reinterpret_cast<uint8_t *>(outRGBDataPtr); | |
150 srcRow += sampler.srcY0() * srcRowBytes; | |
151 for (int y = 0; y < dstHeight; ++y) { | |
152 sampler.next(srcRow); | |
153 srcRow += sampler.srcDY() * srcRowBytes; | |
154 } | |
155 | |
156 return kSuccess; | |
157 | |
158 } else if (ktxFile.isCompressedFormat(SkTextureCompressor::kETC1_Format)) { | |
159 if (!sampler.begin(bm, SkScaledBitmapSampler::kRGB, *this)) { | |
160 return kFailure; | |
161 } | |
162 | |
163 // ETC1 Data is encoded as RGB pixels, so we should extract it as such | |
164 int nPixels = width * height; | |
165 SkAutoMalloc outRGBData(nPixels * 3); | |
166 uint8_t *outRGBDataPtr = reinterpret_cast<uint8_t *>(outRGBData.get()); | |
167 | |
168 // Decode ETC1 | |
169 const uint8_t *buf = reinterpret_cast<const uint8_t *>(ktxFile.pixelData
()); | |
170 if (!SkTextureCompressor::DecompressBufferFromFormat( | |
171 outRGBDataPtr, width*3, buf, width, height, SkTextureCompressor:
:kETC1_Format)) { | |
172 return kFailure; | |
173 } | |
174 | |
175 // Set each of the pixels... | |
176 const int srcRowBytes = width * 3; | |
177 const int dstHeight = sampler.scaledHeight(); | |
178 const uint8_t *srcRow = reinterpret_cast<uint8_t *>(outRGBDataPtr); | |
179 srcRow += sampler.srcY0() * srcRowBytes; | |
180 for (int y = 0; y < dstHeight; ++y) { | |
181 sampler.next(srcRow); | |
182 srcRow += sampler.srcDY() * srcRowBytes; | |
183 } | |
184 | |
185 return kSuccess; | |
186 | |
187 } else if (ktxFile.isRGB8()) { | |
188 | |
189 // Uncompressed RGB data (without alpha) | |
190 if (!sampler.begin(bm, SkScaledBitmapSampler::kRGB, *this)) { | |
191 return kFailure; | |
192 } | |
193 | |
194 // Just need to read RGB pixels | |
195 const int srcRowBytes = width * 3; | |
196 const int dstHeight = sampler.scaledHeight(); | |
197 const uint8_t *srcRow = reinterpret_cast<const uint8_t *>(ktxFile.pixelD
ata()); | |
198 srcRow += sampler.srcY0() * srcRowBytes; | |
199 for (int y = 0; y < dstHeight; ++y) { | |
200 sampler.next(srcRow); | |
201 srcRow += sampler.srcDY() * srcRowBytes; | |
202 } | |
203 | |
204 return kSuccess; | |
205 | |
206 } else if (ktxFile.isRGBA8()) { | |
207 | |
208 // Uncompressed RGBA data | |
209 | |
210 // If we know that the image contains premultiplied alpha, then | |
211 // we need to turn off the premultiplier | |
212 SkScaledBitmapSampler::Options opts (*this); | |
213 if (bSrcIsPremul) { | |
214 SkASSERT(bm->alphaType() == kPremul_SkAlphaType); | |
215 SkASSERT(!this->getRequireUnpremultipliedColors()); | |
216 | |
217 opts.fPremultiplyAlpha = false; | |
218 } | |
219 | |
220 if (!sampler.begin(bm, SkScaledBitmapSampler::kRGBA, opts)) { | |
221 return kFailure; | |
222 } | |
223 | |
224 // Just need to read RGBA pixels | |
225 const int srcRowBytes = width * 4; | |
226 const int dstHeight = sampler.scaledHeight(); | |
227 const uint8_t *srcRow = reinterpret_cast<const uint8_t *>(ktxFile.pixelD
ata()); | |
228 srcRow += sampler.srcY0() * srcRowBytes; | |
229 for (int y = 0; y < dstHeight; ++y) { | |
230 sampler.next(srcRow); | |
231 srcRow += sampler.srcDY() * srcRowBytes; | |
232 } | |
233 | |
234 return kSuccess; | |
235 } | |
236 | |
237 return kFailure; | |
238 } | |
239 | |
240 /////////////////////////////////////////////////////////////////////////////// | 19 /////////////////////////////////////////////////////////////////////////////// |
241 | 20 |
242 // KTX Image Encoder | 21 // KTX Image Encoder |
243 // | 22 // |
| 23 // KTX is a general texture data storage file format ratified by the Khronos Gro
up. As an |
| 24 // overview, a KTX file contains all of the appropriate values needed to fully s
pecify a |
| 25 // texture in an OpenGL application, including the use of compressed data. |
| 26 // |
244 // This encoder takes a best guess at how to encode the bitmap passed to it. If | 27 // This encoder takes a best guess at how to encode the bitmap passed to it. If |
245 // there is an installed discardable pixel ref with existing PKM data, then we | 28 // there is an installed discardable pixel ref with existing PKM data, then we |
246 // will repurpose the existing ETC1 data into a KTX file. If the data contains | 29 // will repurpose the existing ETC1 data into a KTX file. If the data contains |
247 // KTX data, then we simply return a copy of the same data. For all other files, | 30 // KTX data, then we simply return a copy of the same data. For all other files, |
248 // the underlying KTX library tries to do its best to encode the appropriate | 31 // the underlying KTX library tries to do its best to encode the appropriate |
249 // data specified by the bitmap based on the config. (i.e. kAlpha8_Config will | 32 // data specified by the bitmap based on the config. (i.e. kAlpha8_Config will |
250 // be represented as a full resolution 8-bit image dump with the appropriate | 33 // be represented as a full resolution 8-bit image dump with the appropriate |
251 // OpenGL defines in the header). | 34 // OpenGL defines in the header). |
252 | 35 |
253 class SkKTXImageEncoder : public SkImageEncoder { | 36 class SkKTXImageEncoder : public SkImageEncoder { |
(...skipping 43 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
297 return false; | 80 return false; |
298 } | 81 } |
299 | 82 |
300 // Advance pointer to etc1 data. | 83 // Advance pointer to etc1 data. |
301 bytes += ETC_PKM_HEADER_SIZE; | 84 bytes += ETC_PKM_HEADER_SIZE; |
302 | 85 |
303 return SkKTXFile::WriteETC1ToKTX(stream, bytes, width, height); | 86 return SkKTXFile::WriteETC1ToKTX(stream, bytes, width, height); |
304 } | 87 } |
305 | 88 |
306 ////////////////////////////////////////////////////////////////////////////////
///////// | 89 ////////////////////////////////////////////////////////////////////////////////
///////// |
307 DEFINE_DECODER_CREATOR(KTXImageDecoder); | |
308 DEFINE_ENCODER_CREATOR(KTXImageEncoder); | 90 DEFINE_ENCODER_CREATOR(KTXImageEncoder); |
309 ////////////////////////////////////////////////////////////////////////////////
///////// | 91 ////////////////////////////////////////////////////////////////////////////////
///////// |
310 | 92 |
311 static SkImageDecoder* sk_libktx_dfactory(SkStreamRewindable* stream) { | |
312 if (SkKTXFile::is_ktx(stream)) { | |
313 return new SkKTXImageDecoder; | |
314 } | |
315 return nullptr; | |
316 } | |
317 | |
318 static SkImageDecoder::Format get_format_ktx(SkStreamRewindable* stream) { | |
319 if (SkKTXFile::is_ktx(stream)) { | |
320 return SkImageDecoder::kKTX_Format; | |
321 } | |
322 return SkImageDecoder::kUnknown_Format; | |
323 } | |
324 | |
325 SkImageEncoder* sk_libktx_efactory(SkImageEncoder::Type t) { | 93 SkImageEncoder* sk_libktx_efactory(SkImageEncoder::Type t) { |
326 return (SkImageEncoder::kKTX_Type == t) ? new SkKTXImageEncoder : nullptr; | 94 return (SkImageEncoder::kKTX_Type == t) ? new SkKTXImageEncoder : nullptr; |
327 } | 95 } |
328 | 96 |
329 static SkImageDecoder_DecodeReg gReg(sk_libktx_dfactory); | |
330 static SkImageDecoder_FormatReg gFormatReg(get_format_ktx); | |
331 static SkImageEncoder_EncodeReg gEReg(sk_libktx_efactory); | 97 static SkImageEncoder_EncodeReg gEReg(sk_libktx_efactory); |
OLD | NEW |