blob: 6b50a09f268c3889e99831098980486aef235af7 [file] [log] [blame]
emmaleer8f4ba762015-08-14 07:44:46 -07001/*
2 * Copyright 2015 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 "SkCodecPriv.h"
9#include "SkScaledCodec.h"
10#include "SkStream.h"
11#include "SkWebpCodec.h"
12
13
14SkCodec* SkScaledCodec::NewFromStream(SkStream* stream) {
scroggo46c57472015-09-30 08:57:13 -070015 SkAutoTDelete<SkCodec> codec(SkCodec::NewFromStream(stream));
16 if (nullptr == codec) {
halcanary96fcdcc2015-08-27 07:41:13 -070017 return nullptr;
emmaleer8f4ba762015-08-14 07:44:46 -070018 }
19
scroggo2c3b2182015-10-09 08:40:59 -070020 switch (codec->getEncodedFormat()) {
21 case kWEBP_SkEncodedFormat:
22 // Webp codec supports scaling and subsetting natively
23 return codec.detach();
24 case kPNG_SkEncodedFormat:
25 case kJPEG_SkEncodedFormat:
26 // wrap in new SkScaledCodec
27 return new SkScaledCodec(codec.detach());
28 default:
29 // FIXME: SkScaledCodec is temporarily disabled for other formats
30 // while focusing on the formats that are supported by
31 // BitmapRegionDecoder.
32 return nullptr;
33 }
emmaleer8f4ba762015-08-14 07:44:46 -070034}
35
36SkCodec* SkScaledCodec::NewFromData(SkData* data) {
37 if (!data) {
halcanary96fcdcc2015-08-27 07:41:13 -070038 return nullptr;
emmaleer8f4ba762015-08-14 07:44:46 -070039 }
halcanary385fe4d2015-08-26 13:07:48 -070040 return NewFromStream(new SkMemoryStream(data));
emmaleer8f4ba762015-08-14 07:44:46 -070041}
42
scroggo46c57472015-09-30 08:57:13 -070043SkScaledCodec::SkScaledCodec(SkCodec* codec)
44 : INHERITED(codec->getInfo(), nullptr)
45 , fCodec(codec)
emmaleer8f4ba762015-08-14 07:44:46 -070046{}
47
48SkScaledCodec::~SkScaledCodec() {}
49
msarettcc7f3052015-10-05 14:20:27 -070050bool SkScaledCodec::onRewind() {
51 return fCodec->onRewind();
52}
53
msaretta83593b2015-08-18 08:03:58 -070054static SkISize best_scaled_dimensions(const SkISize& origDims, const SkISize& nativeDims,
emmaleer8f4ba762015-08-14 07:44:46 -070055 const SkISize& scaledCodecDims, float desiredScale) {
56 if (nativeDims == scaledCodecDims) {
57 // does not matter which to return if equal. Return here to skip below calculations
58 return nativeDims;
59 }
60 float idealWidth = origDims.width() * desiredScale;
61 float idealHeight = origDims.height() * desiredScale;
62
63 // calculate difference between native dimensions and ideal dimensions
64 float nativeWDiff = SkTAbs(idealWidth - nativeDims.width());
65 float nativeHDiff = SkTAbs(idealHeight - nativeDims.height());
msaretta83593b2015-08-18 08:03:58 -070066 float nativeDiff = nativeWDiff + nativeHDiff;
67
68 // Native scaling is preferred to sampling. If we can scale natively to
69 // within one of the ideal value, we should choose to scale natively.
70 if (nativeWDiff < 1.0f && nativeHDiff < 1.0f) {
71 return nativeDims;
72 }
emmaleer8f4ba762015-08-14 07:44:46 -070073
74 // calculate difference between scaledCodec dimensions and ideal dimensions
75 float scaledCodecWDiff = SkTAbs(idealWidth - scaledCodecDims.width());
76 float scaledCodecHDiff = SkTAbs(idealHeight - scaledCodecDims.height());
msaretta83593b2015-08-18 08:03:58 -070077 float scaledCodecDiff = scaledCodecWDiff + scaledCodecHDiff;
emmaleer8f4ba762015-08-14 07:44:46 -070078
79 // return dimensions closest to ideal dimensions.
80 // If the differences are equal, return nativeDims, as native scaling is more efficient.
81 return nativeDiff > scaledCodecDiff ? scaledCodecDims : nativeDims;
msaretta83593b2015-08-18 08:03:58 -070082}
emmaleer8f4ba762015-08-14 07:44:46 -070083
emmaleer8f4ba762015-08-14 07:44:46 -070084/*
85 * Return a valid set of output dimensions for this decoder, given an input scale
86 */
87SkISize SkScaledCodec::onGetScaledDimensions(float desiredScale) const {
scroggo46c57472015-09-30 08:57:13 -070088 SkISize nativeDimensions = fCodec->getScaledDimensions(desiredScale);
emmaleer8f4ba762015-08-14 07:44:46 -070089 // support scaling down by integer numbers. Ex: 1/2, 1/3, 1/4 ...
90 SkISize scaledCodecDimensions;
91 if (desiredScale > 0.5f) {
92 // sampleSize = 1
scroggo46c57472015-09-30 08:57:13 -070093 scaledCodecDimensions = fCodec->getInfo().dimensions();
emmaleer8f4ba762015-08-14 07:44:46 -070094 }
95 // sampleSize determines the step size between samples
96 // Ex: sampleSize = 2, sample every second pixel in x and y directions
msarett6c50a8f2015-09-18 11:24:44 -070097 int sampleSize = int ((1.0f / desiredScale) + 0.5f);
emmaleer8f4ba762015-08-14 07:44:46 -070098
99 int scaledWidth = get_scaled_dimension(this->getInfo().width(), sampleSize);
100 int scaledHeight = get_scaled_dimension(this->getInfo().height(), sampleSize);
101
102 // Return the calculated output dimensions for the given scale
103 scaledCodecDimensions = SkISize::Make(scaledWidth, scaledHeight);
104
msaretta83593b2015-08-18 08:03:58 -0700105 return best_scaled_dimensions(this->getInfo().dimensions(), nativeDimensions,
emmaleer8f4ba762015-08-14 07:44:46 -0700106 scaledCodecDimensions, desiredScale);
107}
108
109// check if scaling to dstInfo size from srcInfo size using sampleSize is possible
scroggoe7fc14b2015-10-02 13:14:46 -0700110static bool scaling_supported(const SkISize& dstDim, const SkISize& srcDim,
emmaleer8f4ba762015-08-14 07:44:46 -0700111 int* sampleX, int* sampleY) {
scroggoe7fc14b2015-10-02 13:14:46 -0700112 SkScaledCodec::ComputeSampleSize(dstDim, srcDim, sampleX, sampleY);
113 const int dstWidth = dstDim.width();
114 const int dstHeight = dstDim.height();
115 const int srcWidth = srcDim.width();
116 const int srcHeight = srcDim.height();
emmaleer8f4ba762015-08-14 07:44:46 -0700117 // only support down sampling, not up sampling
118 if (dstWidth > srcWidth || dstHeight > srcHeight) {
119 return false;
120 }
121 // check that srcWidth is scaled down by an integer value
122 if (get_scaled_dimension(srcWidth, *sampleX) != dstWidth) {
123 return false;
124 }
125 // check that src height is scaled down by an integer value
126 if (get_scaled_dimension(srcHeight, *sampleY) != dstHeight) {
127 return false;
128 }
129 // sampleX and sampleY should be equal unless the original sampleSize requested was larger
130 // than srcWidth or srcHeight. If so, the result of this is dstWidth or dstHeight = 1.
131 // This functionality allows for tall thin images to still be scaled down by scaling factors.
132 if (*sampleX != *sampleY){
133 if (1 != dstWidth && 1 != dstHeight) {
134 return false;
135 }
136 }
137 return true;
138}
139
scroggoe7fc14b2015-10-02 13:14:46 -0700140bool SkScaledCodec::onDimensionsSupported(const SkISize& dim) {
141 // Check with fCodec first. No need to call the non-virtual version, which
142 // just checks if it matches the original, since a match means this method
143 // will not be called.
144 if (fCodec->onDimensionsSupported(dim)) {
145 return true;
146 }
147
148 // FIXME: These variables are unused, but are needed by scaling_supported.
149 // This class could also cache these values, and avoid calling this in
150 // onGetPixels (since getPixels already calls it).
151 int sampleX;
152 int sampleY;
153 return scaling_supported(dim, this->getInfo().dimensions(), &sampleX, &sampleY);
154}
155
emmaleer8f4ba762015-08-14 07:44:46 -0700156// calculates sampleSize in x and y direction
scroggoe7fc14b2015-10-02 13:14:46 -0700157void SkScaledCodec::ComputeSampleSize(const SkISize& dstDim, const SkISize& srcDim,
emmaleer8f4ba762015-08-14 07:44:46 -0700158 int* sampleXPtr, int* sampleYPtr) {
scroggoe7fc14b2015-10-02 13:14:46 -0700159 int srcWidth = srcDim.width();
160 int dstWidth = dstDim.width();
161 int srcHeight = srcDim.height();
162 int dstHeight = dstDim.height();
emmaleer8f4ba762015-08-14 07:44:46 -0700163
164 int sampleX = srcWidth / dstWidth;
165 int sampleY = srcHeight / dstHeight;
166
167 // only support down sampling, not up sampling
168 SkASSERT(dstWidth <= srcWidth);
169 SkASSERT(dstHeight <= srcHeight);
170
171 // sampleX and sampleY should be equal unless the original sampleSize requested was
172 // larger than srcWidth or srcHeight.
173 // If so, the result of this is dstWidth or dstHeight = 1. This functionality
174 // allows for tall thin images to still be scaled down by scaling factors.
175
176 if (sampleX != sampleY){
177 if (1 != dstWidth && 1 != dstHeight) {
178
179 // rounding during onGetScaledDimensions can cause different sampleSizes
180 // Ex: srcWidth = 79, srcHeight = 20, sampleSize = 10
181 // dstWidth = 7, dstHeight = 2, sampleX = 79/7 = 11, sampleY = 20/2 = 10
182 // correct for this rounding by comparing width to sampleY and height to sampleX
183
184 if (get_scaled_dimension(srcWidth, sampleY) == dstWidth) {
185 sampleX = sampleY;
186 } else if (get_scaled_dimension(srcHeight, sampleX) == dstHeight) {
187 sampleY = sampleX;
188 }
189 }
190 }
191
192 if (sampleXPtr) {
193 *sampleXPtr = sampleX;
194 }
195 if (sampleYPtr) {
196 *sampleYPtr = sampleY;
197 }
198}
199
200// TODO: Implement subsetting in onGetPixels which works when and when not sampling
201
202SkCodec::Result SkScaledCodec::onGetPixels(const SkImageInfo& requestedInfo, void* dst,
203 size_t rowBytes, const Options& options,
msarette6dd0042015-10-09 11:07:34 -0700204 SkPMColor ctable[], int* ctableCount,
205 int* rowsDecoded) {
emmaleer8f4ba762015-08-14 07:44:46 -0700206
207 if (options.fSubset) {
208 // Subsets are not supported.
209 return kUnimplemented;
emmaleer8f4ba762015-08-14 07:44:46 -0700210 }
211
scroggoe7fc14b2015-10-02 13:14:46 -0700212 if (fCodec->dimensionsSupported(requestedInfo.dimensions())) {
msarette6dd0042015-10-09 11:07:34 -0700213 // Make sure that the parent class does not fill on an incomplete decode, since
214 // fCodec will take care of filling the uninitialized lines.
215 *rowsDecoded = requestedInfo.height();
scroggoe7fc14b2015-10-02 13:14:46 -0700216 return fCodec->getPixels(requestedInfo, dst, rowBytes, &options, ctable, ctableCount);
emmaleer8f4ba762015-08-14 07:44:46 -0700217 }
scroggo46c57472015-09-30 08:57:13 -0700218
emmaleer8f4ba762015-08-14 07:44:46 -0700219 // scaling requested
220 int sampleX;
221 int sampleY;
scroggoe7fc14b2015-10-02 13:14:46 -0700222 if (!scaling_supported(requestedInfo.dimensions(), fCodec->getInfo().dimensions(),
223 &sampleX, &sampleY)) {
224 // onDimensionsSupported would have returned false, meaning we should never reach here.
225 SkASSERT(false);
emmaleer8f4ba762015-08-14 07:44:46 -0700226 return kInvalidScale;
227 }
scroggoe7fc14b2015-10-02 13:14:46 -0700228
emmaleer8f4ba762015-08-14 07:44:46 -0700229 // set first sample pixel in y direction
scroggoe7fc14b2015-10-02 13:14:46 -0700230 const int Y0 = get_start_coord(sampleY);
emmaleer8f4ba762015-08-14 07:44:46 -0700231
scroggoe7fc14b2015-10-02 13:14:46 -0700232 const int dstHeight = requestedInfo.height();
233 const int srcWidth = fCodec->getInfo().width();
234 const int srcHeight = fCodec->getInfo().height();
emmaleer8f4ba762015-08-14 07:44:46 -0700235
scroggoe7fc14b2015-10-02 13:14:46 -0700236 const SkImageInfo info = requestedInfo.makeWH(srcWidth, srcHeight);
237
238 Result result = fCodec->startScanlineDecode(info, &options, ctable, ctableCount);
239
emmaleer8f4ba762015-08-14 07:44:46 -0700240 if (kSuccess != result) {
241 return result;
242 }
emmaleer8f4ba762015-08-14 07:44:46 -0700243
msarette6dd0042015-10-09 11:07:34 -0700244 SkSampler* sampler = fCodec->getSampler(true);
scroggoe7fc14b2015-10-02 13:14:46 -0700245 if (!sampler) {
246 return kUnimplemented;
247 }
248
249 if (sampler->setSampleX(sampleX) != requestedInfo.width()) {
250 return kInvalidScale;
251 }
252
scroggo46c57472015-09-30 08:57:13 -0700253 switch(fCodec->getScanlineOrder()) {
254 case SkCodec::kTopDown_SkScanlineOrder: {
msarette6dd0042015-10-09 11:07:34 -0700255 if (!fCodec->skipScanlines(Y0)) {
256 *rowsDecoded = 0;
257 return kIncompleteInput;
msarett5406d6f2015-08-31 06:55:13 -0700258 }
259 for (int y = 0; y < dstHeight; y++) {
msarette6dd0042015-10-09 11:07:34 -0700260 if (1 != fCodec->getScanlines(dst, 1, rowBytes)) {
261 // The failed call to getScanlines() will take care of
262 // filling the failed row, so we indicate that we have
263 // decoded (y + 1) rows.
264 *rowsDecoded = y + 1;
265 return kIncompleteInput;
msarett5406d6f2015-08-31 06:55:13 -0700266 }
267 if (y < dstHeight - 1) {
msarette6dd0042015-10-09 11:07:34 -0700268 if (!fCodec->skipScanlines(sampleY - 1)) {
269 *rowsDecoded = y + 1;
270 return kIncompleteInput;
msarett5406d6f2015-08-31 06:55:13 -0700271 }
272 }
273 dst = SkTAddOffset<void>(dst, rowBytes);
274 }
msarette6dd0042015-10-09 11:07:34 -0700275 return kSuccess;
emmaleer8f4ba762015-08-14 07:44:46 -0700276 }
scroggo46c57472015-09-30 08:57:13 -0700277 case SkCodec::kBottomUp_SkScanlineOrder:
278 case SkCodec::kOutOfOrder_SkScanlineOrder: {
msarette6dd0042015-10-09 11:07:34 -0700279 Result result = kSuccess;
280 int y;
281 for (y = 0; y < srcHeight; y++) {
scroggo46c57472015-09-30 08:57:13 -0700282 int srcY = fCodec->nextScanline();
msarett5406d6f2015-08-31 06:55:13 -0700283 if (is_coord_necessary(srcY, sampleY, dstHeight)) {
284 void* dstPtr = SkTAddOffset<void>(dst, rowBytes * get_dst_coord(srcY, sampleY));
msarette6dd0042015-10-09 11:07:34 -0700285 if (1 != fCodec->getScanlines(dstPtr, 1, rowBytes)) {
286 result = kIncompleteInput;
287 break;
msarett5406d6f2015-08-31 06:55:13 -0700288 }
289 } else {
msarette6dd0042015-10-09 11:07:34 -0700290 if (!fCodec->skipScanlines(1)) {
291 result = kIncompleteInput;
292 break;
msarett5406d6f2015-08-31 06:55:13 -0700293 }
294 }
295 }
msarette6dd0042015-10-09 11:07:34 -0700296
297 // We handle filling uninitialized memory here instead of in the parent class.
298 // The parent class does not know that we are sampling.
299 if (kIncompleteInput == result) {
300 const uint32_t fillValue = fCodec->getFillValue(requestedInfo.colorType(),
301 requestedInfo.alphaType());
302 for (; y < srcHeight; y++) {
303 int srcY = fCodec->outputScanline(y);
304 if (is_coord_necessary(srcY, sampleY, dstHeight)) {
305 void* dstRow = SkTAddOffset<void>(dst,
306 rowBytes * get_dst_coord(srcY, sampleY));
307 SkSampler::Fill(requestedInfo.makeWH(requestedInfo.width(), 1), dstRow,
308 rowBytes, fillValue, options.fZeroInitialized);
309 }
310 }
311 *rowsDecoded = dstHeight;
312 }
msarett10522ff2015-09-07 08:54:01 -0700313 return result;
emmaleer8f4ba762015-08-14 07:44:46 -0700314 }
scroggo46c57472015-09-30 08:57:13 -0700315 case SkCodec::kNone_SkScanlineOrder: {
msarett5406d6f2015-08-31 06:55:13 -0700316 SkAutoMalloc storage(srcHeight * rowBytes);
317 uint8_t* storagePtr = static_cast<uint8_t*>(storage.get());
msarette6dd0042015-10-09 11:07:34 -0700318 int scanlines = fCodec->getScanlines(storagePtr, srcHeight, rowBytes);
msarett5406d6f2015-08-31 06:55:13 -0700319 storagePtr += Y0 * rowBytes;
msarette6dd0042015-10-09 11:07:34 -0700320 scanlines -= Y0;
321 int y = 0;
322 while (y < dstHeight && scanlines > 0) {
msarett5406d6f2015-08-31 06:55:13 -0700323 memcpy(dst, storagePtr, rowBytes);
324 storagePtr += sampleY * rowBytes;
325 dst = SkTAddOffset<void>(dst, rowBytes);
msarette6dd0042015-10-09 11:07:34 -0700326 scanlines -= sampleY;
327 y++;
emmaleer8f4ba762015-08-14 07:44:46 -0700328 }
msarette6dd0042015-10-09 11:07:34 -0700329 if (y < dstHeight) {
330 // fCodec has already handled filling uninitialized memory.
331 *rowsDecoded = dstHeight;
332 return kIncompleteInput;
333 }
334 return kSuccess;
emmaleer8f4ba762015-08-14 07:44:46 -0700335 }
msarett5406d6f2015-08-31 06:55:13 -0700336 default:
337 SkASSERT(false);
338 return kUnimplemented;
emmaleer8f4ba762015-08-14 07:44:46 -0700339 }
emmaleer8f4ba762015-08-14 07:44:46 -0700340}
msarette6dd0042015-10-09 11:07:34 -0700341
342uint32_t SkScaledCodec::onGetFillValue(SkColorType colorType, SkAlphaType alphaType) const {
343 return fCodec->onGetFillValue(colorType, alphaType);
344}
345
346SkCodec::SkScanlineOrder SkScaledCodec::onGetScanlineOrder() const {
347 return fCodec->onGetScanlineOrder();
348}