blob: e0158b6b4ef005d035ac6ef54576debdb28b7b98 [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) {
15 bool isWebp = SkWebpCodec::IsWebp(stream);
16 if (!stream->rewind()) {
17 return NULL;
18 }
19 if (isWebp) {
20 // Webp codec supports scaling and subsetting natively
21 return SkWebpCodec::NewFromStream(stream);
22 }
23
24 SkAutoTDelete<SkScanlineDecoder> scanlineDecoder(SkScanlineDecoder::NewFromStream(stream));
25 if (NULL == scanlineDecoder) {
26 return NULL;
27 }
28
29 // wrap in new SkScaledCodec
30 return SkNEW_ARGS(SkScaledCodec, (scanlineDecoder.detach()));
31}
32
33SkCodec* SkScaledCodec::NewFromData(SkData* data) {
34 if (!data) {
35 return NULL;
36 }
37 return NewFromStream(SkNEW_ARGS(SkMemoryStream, (data)));
38}
39
40SkScaledCodec::SkScaledCodec(SkScanlineDecoder* scanlineDecoder)
41 : INHERITED(scanlineDecoder->getInfo(), NULL)
42 , fScanlineDecoder(scanlineDecoder)
43{}
44
45SkScaledCodec::~SkScaledCodec() {}
46
47// returns a scaled dimension based on the original dimension and the sampleSize
48// NOTE: we round down here for scaled dimension to match the behavior of SkImageDecoder
49static int get_scaled_dimension(int srcDimension, int sampleSize) {
50 if (sampleSize > srcDimension) {
51 return 1;
52 }
53 return srcDimension / sampleSize;
54}
55
56static SkISize best_scaled_dimensions(const SkISize& origDims, const SkISize& nativeDims,
57 const SkISize& scaledCodecDims, float desiredScale) {
58 if (nativeDims == scaledCodecDims) {
59 // does not matter which to return if equal. Return here to skip below calculations
60 return nativeDims;
61 }
62 float idealWidth = origDims.width() * desiredScale;
63 float idealHeight = origDims.height() * desiredScale;
64
65 // calculate difference between native dimensions and ideal dimensions
66 float nativeWDiff = SkTAbs(idealWidth - nativeDims.width());
67 float nativeHDiff = SkTAbs(idealHeight - nativeDims.height());
68 float nativeDiff = (nativeWDiff + nativeHDiff) / 2;
69
70 // calculate difference between scaledCodec dimensions and ideal dimensions
71 float scaledCodecWDiff = SkTAbs(idealWidth - scaledCodecDims.width());
72 float scaledCodecHDiff = SkTAbs(idealHeight - scaledCodecDims.height());
73 float scaledCodecDiff = (scaledCodecWDiff + scaledCodecHDiff) / 2;
74
75 // return dimensions closest to ideal dimensions.
76 // If the differences are equal, return nativeDims, as native scaling is more efficient.
77 return nativeDiff > scaledCodecDiff ? scaledCodecDims : nativeDims;
78
79}
80/*
81 * Return a valid set of output dimensions for this decoder, given an input scale
82 */
83SkISize SkScaledCodec::onGetScaledDimensions(float desiredScale) const {
84 SkISize nativeDimensions = fScanlineDecoder->getScaledDimensions(desiredScale);
85 // support scaling down by integer numbers. Ex: 1/2, 1/3, 1/4 ...
86 SkISize scaledCodecDimensions;
87 if (desiredScale > 0.5f) {
88 // sampleSize = 1
89 scaledCodecDimensions = fScanlineDecoder->getInfo().dimensions();
90 }
91 // sampleSize determines the step size between samples
92 // Ex: sampleSize = 2, sample every second pixel in x and y directions
93 int sampleSize = int(1 / desiredScale);
94
95 int scaledWidth = get_scaled_dimension(this->getInfo().width(), sampleSize);
96 int scaledHeight = get_scaled_dimension(this->getInfo().height(), sampleSize);
97
98 // Return the calculated output dimensions for the given scale
99 scaledCodecDimensions = SkISize::Make(scaledWidth, scaledHeight);
100
101 return best_scaled_dimensions(this->getInfo().dimensions(), nativeDimensions,
102 scaledCodecDimensions, desiredScale);
103}
104
105// check if scaling to dstInfo size from srcInfo size using sampleSize is possible
106static bool scaling_supported(const SkImageInfo& dstInfo, const SkImageInfo& srcInfo,
107 int* sampleX, int* sampleY) {
108 SkScaledCodec::ComputeSampleSize(dstInfo, srcInfo, sampleX, sampleY);
109 const int dstWidth = dstInfo.width();
110 const int dstHeight = dstInfo.height();
111 const int srcWidth = srcInfo.width();
112 const int srcHeight = srcInfo.height();
113 // only support down sampling, not up sampling
114 if (dstWidth > srcWidth || dstHeight > srcHeight) {
115 return false;
116 }
117 // check that srcWidth is scaled down by an integer value
118 if (get_scaled_dimension(srcWidth, *sampleX) != dstWidth) {
119 return false;
120 }
121 // check that src height is scaled down by an integer value
122 if (get_scaled_dimension(srcHeight, *sampleY) != dstHeight) {
123 return false;
124 }
125 // sampleX and sampleY should be equal unless the original sampleSize requested was larger
126 // than srcWidth or srcHeight. If so, the result of this is dstWidth or dstHeight = 1.
127 // This functionality allows for tall thin images to still be scaled down by scaling factors.
128 if (*sampleX != *sampleY){
129 if (1 != dstWidth && 1 != dstHeight) {
130 return false;
131 }
132 }
133 return true;
134}
135
136// calculates sampleSize in x and y direction
137void SkScaledCodec::ComputeSampleSize(const SkImageInfo& dstInfo, const SkImageInfo& srcInfo,
138 int* sampleXPtr, int* sampleYPtr) {
139 int srcWidth = srcInfo.width();
140 int dstWidth = dstInfo.width();
141 int srcHeight = srcInfo.height();
142 int dstHeight = dstInfo.height();
143
144 int sampleX = srcWidth / dstWidth;
145 int sampleY = srcHeight / dstHeight;
146
147 // only support down sampling, not up sampling
148 SkASSERT(dstWidth <= srcWidth);
149 SkASSERT(dstHeight <= srcHeight);
150
151 // sampleX and sampleY should be equal unless the original sampleSize requested was
152 // larger than srcWidth or srcHeight.
153 // If so, the result of this is dstWidth or dstHeight = 1. This functionality
154 // allows for tall thin images to still be scaled down by scaling factors.
155
156 if (sampleX != sampleY){
157 if (1 != dstWidth && 1 != dstHeight) {
158
159 // rounding during onGetScaledDimensions can cause different sampleSizes
160 // Ex: srcWidth = 79, srcHeight = 20, sampleSize = 10
161 // dstWidth = 7, dstHeight = 2, sampleX = 79/7 = 11, sampleY = 20/2 = 10
162 // correct for this rounding by comparing width to sampleY and height to sampleX
163
164 if (get_scaled_dimension(srcWidth, sampleY) == dstWidth) {
165 sampleX = sampleY;
166 } else if (get_scaled_dimension(srcHeight, sampleX) == dstHeight) {
167 sampleY = sampleX;
168 }
169 }
170 }
171
172 if (sampleXPtr) {
173 *sampleXPtr = sampleX;
174 }
175 if (sampleYPtr) {
176 *sampleYPtr = sampleY;
177 }
178}
179
180// TODO: Implement subsetting in onGetPixels which works when and when not sampling
181
182SkCodec::Result SkScaledCodec::onGetPixels(const SkImageInfo& requestedInfo, void* dst,
183 size_t rowBytes, const Options& options,
184 SkPMColor ctable[], int* ctableCount) {
185
186 if (options.fSubset) {
187 // Subsets are not supported.
188 return kUnimplemented;
189 }
190
191 Result result = fScanlineDecoder->start(requestedInfo, &options, ctable, ctableCount);
192 if (kSuccess == result) {
193 // native decode supported
194 return fScanlineDecoder->getScanlines(dst, requestedInfo.height(), rowBytes);
195
196 }
197
198 if (kInvalidScale != result) {
199 // no scaling requested
200 return result;
201 }
202
203 // scaling requested
204 int sampleX;
205 int sampleY;
206 if (!scaling_supported(requestedInfo, fScanlineDecoder->getInfo(), &sampleX, &sampleY)) {
207 return kInvalidScale;
208 }
209 // set first sample pixel in y direction
210 int Y0 = sampleY >> 1;
211
212 int dstHeight = requestedInfo.height();
213 int srcHeight = fScanlineDecoder->getInfo().height();
214
215 SkImageInfo info = requestedInfo;
216 // use original height as scanlineDecoder does not support y sampling natively
217 info = info.makeWH(requestedInfo.width(), srcHeight);
218
219 // update scanlineDecoder with new info
220 result = fScanlineDecoder->start(info, &options, ctable, ctableCount);
221 if (kSuccess != result) {
222 return result;
223 }
224
225 const bool requiresPostYSampling = fScanlineDecoder->requiresPostYSampling();
226
227 if (requiresPostYSampling) {
228 SkAutoMalloc storage(srcHeight * rowBytes);
229 uint8_t* storagePtr = static_cast<uint8_t*>(storage.get());
230 result = fScanlineDecoder->getScanlines(storagePtr, srcHeight, rowBytes);
231 if (kSuccess != result) {
232 return result;
233 }
234 storagePtr += Y0 * rowBytes;
235 for (int y = 0; y < dstHeight; y++) {
236 memcpy(dst, storagePtr, rowBytes);
237 storagePtr += sampleY * rowBytes;
238 dst = SkTAddOffset<void>(dst, rowBytes);
239 }
240 } else {
241 // does not require post y sampling
242 result = fScanlineDecoder->skipScanlines(Y0);
243 if (kSuccess != result) {
244 return result;
245 }
246 for (int y = 0; y < dstHeight; y++) {
247 result = fScanlineDecoder->getScanlines(dst, 1, rowBytes);
248 if (kSuccess != result) {
249 return result;
250 }
251 if (y < dstHeight - 1) {
252 result = fScanlineDecoder->skipScanlines(sampleY - 1);
253 if (kSuccess != result) {
254 return result;
255 }
256 }
257 dst = SkTAddOffset<void>(dst, rowBytes);
258 }
259 }
260 return kSuccess;
261}