blob: 3f71b015c56cb18803b5d202236de5d31fbde399 [file] [log] [blame]
senorblanco@chromium.org194d7752013-07-24 22:19:24 +00001/*
2 * Copyright 2013 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
commit-bot@chromium.org4b681bc2013-09-13 12:40:02 +00008#include "SkBitmap.h"
9#include "SkBitmapDevice.h"
10#include "SkBitmapSource.h"
senorblanco@chromium.org6776b822014-01-03 21:48:22 +000011#include "SkBlurImageFilter.h"
tfarina@chromium.org8f6884a2014-01-24 20:56:26 +000012#include "SkCanvas.h"
13#include "SkColorFilterImageFilter.h"
14#include "SkColorMatrixFilter.h"
15#include "SkDeviceImageFilterProxy.h"
senorblanco@chromium.org6776b822014-01-03 21:48:22 +000016#include "SkDisplacementMapEffect.h"
17#include "SkDropShadowImageFilter.h"
senorblanco@chromium.org5251e2b2014-02-05 22:36:31 +000018#include "SkFlattenableBuffers.h"
senorblanco@chromium.org97f5fc62014-05-30 20:50:56 +000019#include "SkFlattenableSerialization.h"
senorblanco@chromium.org0a5c2332014-04-29 15:20:39 +000020#include "SkGradientShader.h"
commit-bot@chromium.org4b681bc2013-09-13 12:40:02 +000021#include "SkLightingImageFilter.h"
tfarina@chromium.org8f6884a2014-01-24 20:56:26 +000022#include "SkMatrixConvolutionImageFilter.h"
senorblanco@chromium.orgd5424a42014-04-02 19:20:05 +000023#include "SkMatrixImageFilter.h"
senorblanco@chromium.org6776b822014-01-03 21:48:22 +000024#include "SkMergeImageFilter.h"
25#include "SkMorphologyImageFilter.h"
senorblanco@chromium.org6776b822014-01-03 21:48:22 +000026#include "SkOffsetImageFilter.h"
senorblanco@chromium.org5251e2b2014-02-05 22:36:31 +000027#include "SkPicture.h"
senorblanco@chromium.org910702b2014-05-30 20:36:15 +000028#include "SkPictureImageFilter.h"
robertphillips@google.com770963f2014-04-18 18:04:41 +000029#include "SkPictureRecorder.h"
tfarina@chromium.org8f6884a2014-01-24 20:56:26 +000030#include "SkRect.h"
senorblanco@chromium.org6776b822014-01-03 21:48:22 +000031#include "SkTileImageFilter.h"
32#include "SkXfermodeImageFilter.h"
tfarina@chromium.org8f6884a2014-01-24 20:56:26 +000033#include "Test.h"
senorblanco@chromium.org194d7752013-07-24 22:19:24 +000034
senorblanco@chromium.orgaba651c2014-02-03 22:22:16 +000035#if SK_SUPPORT_GPU
36#include "GrContextFactory.h"
37#include "SkGpuDevice.h"
38#endif
39
tfarina@chromium.org9f9d5822013-12-18 22:15:12 +000040static const int kBitmapSize = 4;
commit-bot@chromium.org4b681bc2013-09-13 12:40:02 +000041
senorblanco@chromium.org5251e2b2014-02-05 22:36:31 +000042namespace {
43
44class MatrixTestImageFilter : public SkImageFilter {
45public:
46 MatrixTestImageFilter(skiatest::Reporter* reporter, const SkMatrix& expectedMatrix)
47 : SkImageFilter(0), fReporter(reporter), fExpectedMatrix(expectedMatrix) {
48 }
49
senorblanco@chromium.org4cb543d2014-03-14 15:44:01 +000050 virtual bool onFilterImage(Proxy*, const SkBitmap& src, const Context& ctx,
senorblanco@chromium.org09373352014-02-05 23:04:28 +000051 SkBitmap* result, SkIPoint* offset) const SK_OVERRIDE {
senorblanco@chromium.org4cb543d2014-03-14 15:44:01 +000052 REPORTER_ASSERT(fReporter, ctx.ctm() == fExpectedMatrix);
senorblanco@chromium.org5251e2b2014-02-05 22:36:31 +000053 return true;
54 }
55
56 SK_DECLARE_PUBLIC_FLATTENABLE_DESERIALIZATION_PROCS(MatrixTestImageFilter)
57
58protected:
59 explicit MatrixTestImageFilter(SkReadBuffer& buffer) : SkImageFilter(0) {
60 fReporter = static_cast<skiatest::Reporter*>(buffer.readFunctionPtr());
61 buffer.readMatrix(&fExpectedMatrix);
62 }
63
64 virtual void flatten(SkWriteBuffer& buffer) const SK_OVERRIDE {
65 buffer.writeFunctionPtr(fReporter);
66 buffer.writeMatrix(fExpectedMatrix);
67 }
68
69private:
70 skiatest::Reporter* fReporter;
71 SkMatrix fExpectedMatrix;
72};
73
74}
75
tfarina@chromium.org9f9d5822013-12-18 22:15:12 +000076static void make_small_bitmap(SkBitmap& bitmap) {
mike@reedtribe.orgdeee4962014-02-13 14:41:43 +000077 bitmap.allocN32Pixels(kBitmapSize, kBitmapSize);
78 SkCanvas canvas(bitmap);
tfarina@chromium.org9f9d5822013-12-18 22:15:12 +000079 canvas.clear(0x00000000);
80 SkPaint darkPaint;
81 darkPaint.setColor(0xFF804020);
82 SkPaint lightPaint;
83 lightPaint.setColor(0xFF244484);
84 const int i = kBitmapSize / 4;
85 for (int y = 0; y < kBitmapSize; y += i) {
86 for (int x = 0; x < kBitmapSize; x += i) {
87 canvas.save();
88 canvas.translate(SkIntToScalar(x), SkIntToScalar(y));
89 canvas.drawRect(SkRect::MakeXYWH(0, 0,
90 SkIntToScalar(i),
91 SkIntToScalar(i)), darkPaint);
92 canvas.drawRect(SkRect::MakeXYWH(SkIntToScalar(i),
93 0,
94 SkIntToScalar(i),
95 SkIntToScalar(i)), lightPaint);
96 canvas.drawRect(SkRect::MakeXYWH(0,
97 SkIntToScalar(i),
98 SkIntToScalar(i),
99 SkIntToScalar(i)), lightPaint);
100 canvas.drawRect(SkRect::MakeXYWH(SkIntToScalar(i),
101 SkIntToScalar(i),
102 SkIntToScalar(i),
103 SkIntToScalar(i)), darkPaint);
104 canvas.restore();
commit-bot@chromium.org4b681bc2013-09-13 12:40:02 +0000105 }
106 }
tfarina@chromium.org9f9d5822013-12-18 22:15:12 +0000107}
senorblanco@chromium.org194d7752013-07-24 22:19:24 +0000108
tfarina@chromium.org9f9d5822013-12-18 22:15:12 +0000109static SkImageFilter* make_scale(float amount, SkImageFilter* input = NULL) {
110 SkScalar s = amount;
111 SkScalar matrix[20] = { s, 0, 0, 0, 0,
112 0, s, 0, 0, 0,
113 0, 0, s, 0, 0,
114 0, 0, 0, s, 0 };
commit-bot@chromium.org727a3522014-02-21 18:46:30 +0000115 SkAutoTUnref<SkColorFilter> filter(SkColorMatrixFilter::Create(matrix));
tfarina@chromium.org9f9d5822013-12-18 22:15:12 +0000116 return SkColorFilterImageFilter::Create(filter, input);
117}
118
119static SkImageFilter* make_grayscale(SkImageFilter* input = NULL, const SkImageFilter::CropRect* cropRect = NULL) {
120 SkScalar matrix[20];
121 memset(matrix, 0, 20 * sizeof(SkScalar));
122 matrix[0] = matrix[5] = matrix[10] = 0.2126f;
123 matrix[1] = matrix[6] = matrix[11] = 0.7152f;
124 matrix[2] = matrix[7] = matrix[12] = 0.0722f;
125 matrix[18] = 1.0f;
commit-bot@chromium.org727a3522014-02-21 18:46:30 +0000126 SkAutoTUnref<SkColorFilter> filter(SkColorMatrixFilter::Create(matrix));
tfarina@chromium.org9f9d5822013-12-18 22:15:12 +0000127 return SkColorFilterImageFilter::Create(filter, input, cropRect);
128}
129
130DEF_TEST(ImageFilter, reporter) {
131 {
132 // Check that two non-clipping color matrices concatenate into a single filter.
133 SkAutoTUnref<SkImageFilter> halfBrightness(make_scale(0.5f));
134 SkAutoTUnref<SkImageFilter> quarterBrightness(make_scale(0.5f, halfBrightness));
135 REPORTER_ASSERT(reporter, NULL == quarterBrightness->getInput(0));
senorblanco@chromium.org194d7752013-07-24 22:19:24 +0000136 }
137
tfarina@chromium.org9f9d5822013-12-18 22:15:12 +0000138 {
139 // Check that a clipping color matrix followed by a grayscale does not concatenate into a single filter.
140 SkAutoTUnref<SkImageFilter> doubleBrightness(make_scale(2.0f));
141 SkAutoTUnref<SkImageFilter> halfBrightness(make_scale(0.5f, doubleBrightness));
142 REPORTER_ASSERT(reporter, NULL != halfBrightness->getInput(0));
senorblanco@chromium.org194d7752013-07-24 22:19:24 +0000143 }
144
tfarina@chromium.org9f9d5822013-12-18 22:15:12 +0000145 {
146 // Check that a color filter image filter without a crop rect can be
147 // expressed as a color filter.
148 SkAutoTUnref<SkImageFilter> gray(make_grayscale());
149 REPORTER_ASSERT(reporter, true == gray->asColorFilter(NULL));
senorblanco@chromium.org194d7752013-07-24 22:19:24 +0000150 }
151
tfarina@chromium.org9f9d5822013-12-18 22:15:12 +0000152 {
153 // Check that a color filter image filter with a crop rect cannot
154 // be expressed as a color filter.
155 SkImageFilter::CropRect cropRect(SkRect::MakeXYWH(0, 0, 100, 100));
156 SkAutoTUnref<SkImageFilter> grayWithCrop(make_grayscale(NULL, &cropRect));
157 REPORTER_ASSERT(reporter, false == grayWithCrop->asColorFilter(NULL));
158 }
159
160 {
senorblanco3df05012014-07-03 11:13:09 -0700161 // Check that two non-commutative matrices are concatenated in
162 // the correct order.
163 SkScalar blueToRedMatrix[20] = { 0 };
164 blueToRedMatrix[2] = blueToRedMatrix[18] = SK_Scalar1;
165 SkScalar redToGreenMatrix[20] = { 0 };
166 redToGreenMatrix[5] = redToGreenMatrix[18] = SK_Scalar1;
167 SkAutoTUnref<SkColorFilter> blueToRed(SkColorMatrixFilter::Create(blueToRedMatrix));
168 SkAutoTUnref<SkImageFilter> filter1(SkColorFilterImageFilter::Create(blueToRed.get()));
169 SkAutoTUnref<SkColorFilter> redToGreen(SkColorMatrixFilter::Create(redToGreenMatrix));
170 SkAutoTUnref<SkImageFilter> filter2(SkColorFilterImageFilter::Create(redToGreen.get(), filter1.get()));
171
172 SkBitmap result;
173 result.allocN32Pixels(kBitmapSize, kBitmapSize);
174
175 SkPaint paint;
176 paint.setColor(SK_ColorBLUE);
177 paint.setImageFilter(filter2.get());
178 SkCanvas canvas(result);
179 canvas.clear(0x0);
180 SkRect rect = SkRect::Make(SkIRect::MakeWH(kBitmapSize, kBitmapSize));
181 canvas.drawRect(rect, paint);
182 uint32_t pixel = *result.getAddr32(0, 0);
183 // The result here should be green, since we have effectively shifted blue to green.
184 REPORTER_ASSERT(reporter, pixel == SK_ColorGREEN);
185 }
186
187 {
tfarina@chromium.org9f9d5822013-12-18 22:15:12 +0000188 // Tests pass by not asserting
189 SkBitmap bitmap, result;
190 make_small_bitmap(bitmap);
mike@reedtribe.orgdeee4962014-02-13 14:41:43 +0000191 result.allocN32Pixels(kBitmapSize, kBitmapSize);
tfarina@chromium.org9f9d5822013-12-18 22:15:12 +0000192
senorblanco@chromium.org194d7752013-07-24 22:19:24 +0000193 {
tfarina@chromium.org9f9d5822013-12-18 22:15:12 +0000194 // This tests for :
195 // 1 ) location at (0,0,1)
196 SkPoint3 location(0, 0, SK_Scalar1);
197 // 2 ) location and target at same value
198 SkPoint3 target(location.fX, location.fY, location.fZ);
199 // 3 ) large negative specular exponent value
200 SkScalar specularExponent = -1000;
201
commit-bot@chromium.orgcac5fd52014-03-10 10:51:58 +0000202 SkAutoTUnref<SkImageFilter> bmSrc(SkBitmapSource::Create(bitmap));
tfarina@chromium.org9f9d5822013-12-18 22:15:12 +0000203 SkPaint paint;
204 paint.setImageFilter(SkLightingImageFilter::CreateSpotLitSpecular(
205 location, target, specularExponent, 180,
206 0xFFFFFFFF, SK_Scalar1, SK_Scalar1, SK_Scalar1,
207 bmSrc))->unref();
208 SkCanvas canvas(result);
209 SkRect r = SkRect::MakeWH(SkIntToScalar(kBitmapSize),
210 SkIntToScalar(kBitmapSize));
211 canvas.drawRect(r, paint);
senorblanco@chromium.org194d7752013-07-24 22:19:24 +0000212 }
senorblanco@chromium.org194d7752013-07-24 22:19:24 +0000213 }
senorblanco@chromium.orgaba651c2014-02-03 22:22:16 +0000214}
senorblanco@chromium.org6776b822014-01-03 21:48:22 +0000215
senorblanco@chromium.orgaba651c2014-02-03 22:22:16 +0000216static void test_crop_rects(SkBaseDevice* device, skiatest::Reporter* reporter) {
217 // Check that all filters offset to their absolute crop rect,
218 // unaffected by the input crop rect.
219 // Tests pass by not asserting.
220 SkBitmap bitmap;
mike@reedtribe.orgdeee4962014-02-13 14:41:43 +0000221 bitmap.allocN32Pixels(100, 100);
senorblanco@chromium.orgaba651c2014-02-03 22:22:16 +0000222 bitmap.eraseARGB(0, 0, 0, 0);
223 SkDeviceImageFilterProxy proxy(device);
senorblanco@chromium.org6776b822014-01-03 21:48:22 +0000224
senorblanco@chromium.orgaba651c2014-02-03 22:22:16 +0000225 SkImageFilter::CropRect inputCropRect(SkRect::MakeXYWH(8, 13, 80, 80));
226 SkImageFilter::CropRect cropRect(SkRect::MakeXYWH(20, 30, 60, 60));
227 SkAutoTUnref<SkImageFilter> input(make_grayscale(NULL, &inputCropRect));
senorblanco@chromium.org6776b822014-01-03 21:48:22 +0000228
senorblanco@chromium.orgaba651c2014-02-03 22:22:16 +0000229 SkAutoTUnref<SkColorFilter> cf(SkColorFilter::CreateModeFilter(SK_ColorRED, SkXfermode::kSrcIn_Mode));
230 SkPoint3 location(0, 0, SK_Scalar1);
231 SkPoint3 target(SK_Scalar1, SK_Scalar1, SK_Scalar1);
232 SkScalar kernel[9] = {
233 SkIntToScalar( 1), SkIntToScalar( 1), SkIntToScalar( 1),
234 SkIntToScalar( 1), SkIntToScalar(-7), SkIntToScalar( 1),
235 SkIntToScalar( 1), SkIntToScalar( 1), SkIntToScalar( 1),
236 };
237 SkISize kernelSize = SkISize::Make(3, 3);
238 SkScalar gain = SK_Scalar1, bias = 0;
senorblanco@chromium.org6776b822014-01-03 21:48:22 +0000239
senorblanco@chromium.orgaba651c2014-02-03 22:22:16 +0000240 SkImageFilter* filters[] = {
241 SkColorFilterImageFilter::Create(cf.get(), input.get(), &cropRect),
commit-bot@chromium.orgcac5fd52014-03-10 10:51:58 +0000242 SkDisplacementMapEffect::Create(SkDisplacementMapEffect::kR_ChannelSelectorType,
243 SkDisplacementMapEffect::kB_ChannelSelectorType,
244 40.0f, input.get(), input.get(), &cropRect),
245 SkBlurImageFilter::Create(SK_Scalar1, SK_Scalar1, input.get(), &cropRect),
246 SkDropShadowImageFilter::Create(SK_Scalar1, SK_Scalar1, SK_Scalar1, SK_Scalar1, SK_ColorGREEN, input.get(), &cropRect),
senorblanco@chromium.orgaba651c2014-02-03 22:22:16 +0000247 SkLightingImageFilter::CreatePointLitDiffuse(location, SK_ColorGREEN, 0, 0, input.get(), &cropRect),
248 SkLightingImageFilter::CreatePointLitSpecular(location, SK_ColorGREEN, 0, 0, 0, input.get(), &cropRect),
commit-bot@chromium.orgcac5fd52014-03-10 10:51:58 +0000249 SkMatrixConvolutionImageFilter::Create(kernelSize, kernel, gain, bias, SkIPoint::Make(1, 1), SkMatrixConvolutionImageFilter::kRepeat_TileMode, false, input.get(), &cropRect),
250 SkMergeImageFilter::Create(input.get(), input.get(), SkXfermode::kSrcOver_Mode, &cropRect),
251 SkOffsetImageFilter::Create(SK_Scalar1, SK_Scalar1, input.get(), &cropRect),
252 SkOffsetImageFilter::Create(SK_Scalar1, SK_Scalar1, input.get(), &cropRect),
253 SkDilateImageFilter::Create(3, 2, input.get(), &cropRect),
254 SkErodeImageFilter::Create(2, 3, input.get(), &cropRect),
255 SkTileImageFilter::Create(inputCropRect.rect(), cropRect.rect(), input.get()),
256 SkXfermodeImageFilter::Create(SkXfermode::Create(SkXfermode::kSrcOver_Mode), input.get(), input.get(), &cropRect),
senorblanco@chromium.orgaba651c2014-02-03 22:22:16 +0000257 };
senorblanco@chromium.org6776b822014-01-03 21:48:22 +0000258
senorblanco@chromium.orgaba651c2014-02-03 22:22:16 +0000259 for (size_t i = 0; i < SK_ARRAY_COUNT(filters); ++i) {
260 SkImageFilter* filter = filters[i];
261 SkBitmap result;
262 SkIPoint offset;
263 SkString str;
senorblanco@chromium.orgf4e1a762014-02-04 00:28:46 +0000264 str.printf("filter %d", static_cast<int>(i));
commit-bot@chromium.orgf7efa502014-04-11 18:57:00 +0000265 SkAutoTUnref<SkImageFilter::Cache> cache(SkImageFilter::Cache::Create(2));
266 SkImageFilter::Context ctx(SkMatrix::I(), SkIRect::MakeLargest(), cache.get());
267 REPORTER_ASSERT_MESSAGE(reporter, filter->filterImage(&proxy, bitmap, ctx,
268 &result, &offset), str.c_str());
senorblanco@chromium.orgaba651c2014-02-03 22:22:16 +0000269 REPORTER_ASSERT_MESSAGE(reporter, offset.fX == 20 && offset.fY == 30, str.c_str());
senorblanco@chromium.org6776b822014-01-03 21:48:22 +0000270 }
senorblanco@chromium.orgaba651c2014-02-03 22:22:16 +0000271
272 for (size_t i = 0; i < SK_ARRAY_COUNT(filters); ++i) {
273 SkSafeUnref(filters[i]);
274 }
275}
276
senorblanco@chromium.org0a5c2332014-04-29 15:20:39 +0000277static SkBitmap make_gradient_circle(int width, int height) {
278 SkBitmap bitmap;
279 SkScalar x = SkIntToScalar(width / 2);
280 SkScalar y = SkIntToScalar(height / 2);
281 SkScalar radius = SkMinScalar(x, y) * 0.8f;
282 bitmap.allocN32Pixels(width, height);
283 SkCanvas canvas(bitmap);
284 canvas.clear(0x00000000);
285 SkColor colors[2];
286 colors[0] = SK_ColorWHITE;
287 colors[1] = SK_ColorBLACK;
288 SkAutoTUnref<SkShader> shader(
289 SkGradientShader::CreateRadial(SkPoint::Make(x, y), radius, colors, NULL, 2,
290 SkShader::kClamp_TileMode)
291 );
292 SkPaint paint;
293 paint.setShader(shader);
294 canvas.drawCircle(x, y, radius, paint);
295 return bitmap;
296}
297
298DEF_TEST(ImageFilterDrawTiled, reporter) {
299 // Check that all filters when drawn tiled (with subsequent clip rects) exactly
300 // match the same filters drawn with a single full-canvas bitmap draw.
301 // Tests pass by not asserting.
302
303 SkAutoTUnref<SkColorFilter> cf(SkColorFilter::CreateModeFilter(SK_ColorRED, SkXfermode::kSrcIn_Mode));
304 SkPoint3 location(0, 0, SK_Scalar1);
305 SkPoint3 target(SK_Scalar1, SK_Scalar1, SK_Scalar1);
306 SkScalar kernel[9] = {
307 SkIntToScalar( 1), SkIntToScalar( 1), SkIntToScalar( 1),
308 SkIntToScalar( 1), SkIntToScalar(-7), SkIntToScalar( 1),
309 SkIntToScalar( 1), SkIntToScalar( 1), SkIntToScalar( 1),
310 };
311 SkISize kernelSize = SkISize::Make(3, 3);
312 SkScalar gain = SK_Scalar1, bias = 0;
senorblanco@chromium.org29ac34e2014-05-28 19:29:25 +0000313 SkScalar five = SkIntToScalar(5);
senorblanco@chromium.org0a5c2332014-04-29 15:20:39 +0000314
315 SkAutoTUnref<SkImageFilter> gradient_source(SkBitmapSource::Create(make_gradient_circle(64, 64)));
senorblanco@chromium.org29ac34e2014-05-28 19:29:25 +0000316 SkAutoTUnref<SkImageFilter> blur(SkBlurImageFilter::Create(five, five));
senorblanco@chromium.orgba31f1d2014-05-07 20:56:08 +0000317 SkMatrix matrix;
senorblanco@chromium.org29ac34e2014-05-28 19:29:25 +0000318
senorblanco@chromium.orgba31f1d2014-05-07 20:56:08 +0000319 matrix.setTranslate(SK_Scalar1, SK_Scalar1);
320 matrix.postRotate(SkIntToScalar(45), SK_Scalar1, SK_Scalar1);
senorblanco@chromium.org0a5c2332014-04-29 15:20:39 +0000321
senorblanco@chromium.org910702b2014-05-30 20:36:15 +0000322 SkRTreeFactory factory;
323 SkPictureRecorder recorder;
324 SkCanvas* recordingCanvas = recorder.beginRecording(64, 64, &factory, 0);
325
326 SkPaint greenPaint;
327 greenPaint.setColor(SK_ColorGREEN);
328 recordingCanvas->drawRect(SkRect::Make(SkIRect::MakeXYWH(10, 10, 30, 20)), greenPaint);
329 SkAutoTUnref<SkPicture> picture(recorder.endRecording());
330 SkAutoTUnref<SkImageFilter> pictureFilter(SkPictureImageFilter::Create(picture.get()));
331
senorblanco@chromium.org0a5c2332014-04-29 15:20:39 +0000332 struct {
333 const char* fName;
334 SkImageFilter* fFilter;
335 } filters[] = {
336 { "color filter", SkColorFilterImageFilter::Create(cf.get()) },
337 { "displacement map", SkDisplacementMapEffect::Create(
338 SkDisplacementMapEffect::kR_ChannelSelectorType,
339 SkDisplacementMapEffect::kB_ChannelSelectorType,
senorblanco@chromium.orgd4db6572014-05-07 20:00:04 +0000340 20.0f, gradient_source.get()) },
senorblanco@chromium.org0a5c2332014-04-29 15:20:39 +0000341 { "blur", SkBlurImageFilter::Create(SK_Scalar1, SK_Scalar1) },
342 { "drop shadow", SkDropShadowImageFilter::Create(
343 SK_Scalar1, SK_Scalar1, SK_Scalar1, SK_Scalar1, SK_ColorGREEN) },
344 { "diffuse lighting", SkLightingImageFilter::CreatePointLitDiffuse(
345 location, SK_ColorGREEN, 0, 0) },
346 { "specular lighting",
347 SkLightingImageFilter::CreatePointLitSpecular(location, SK_ColorGREEN, 0, 0, 0) },
348 { "matrix convolution",
349 SkMatrixConvolutionImageFilter::Create(
350 kernelSize, kernel, gain, bias, SkIPoint::Make(1, 1),
351 SkMatrixConvolutionImageFilter::kRepeat_TileMode, false) },
352 { "merge", SkMergeImageFilter::Create(NULL, NULL, SkXfermode::kSrcOver_Mode) },
353 { "offset", SkOffsetImageFilter::Create(SK_Scalar1, SK_Scalar1) },
354 { "dilate", SkDilateImageFilter::Create(3, 2) },
355 { "erode", SkErodeImageFilter::Create(2, 3) },
356 { "tile", SkTileImageFilter::Create(SkRect::MakeXYWH(0, 0, 50, 50),
357 SkRect::MakeXYWH(0, 0, 100, 100), NULL) },
senorblanco@chromium.orgba31f1d2014-05-07 20:56:08 +0000358 { "matrix", SkMatrixImageFilter::Create(matrix, SkPaint::kLow_FilterLevel) },
senorblanco@chromium.org29ac34e2014-05-28 19:29:25 +0000359 { "blur and offset", SkOffsetImageFilter::Create(five, five, blur.get()) },
senorblanco@chromium.org910702b2014-05-30 20:36:15 +0000360 { "picture and blur", SkBlurImageFilter::Create(five, five, pictureFilter.get()) },
senorblanco@chromium.org0a5c2332014-04-29 15:20:39 +0000361 };
362
363 SkBitmap untiledResult, tiledResult;
364 int width = 64, height = 64;
365 untiledResult.allocN32Pixels(width, height);
366 tiledResult.allocN32Pixels(width, height);
367 SkCanvas tiledCanvas(tiledResult);
368 SkCanvas untiledCanvas(untiledResult);
senorblanco@chromium.orgd4db6572014-05-07 20:00:04 +0000369 int tileSize = 8;
senorblanco@chromium.org0a5c2332014-04-29 15:20:39 +0000370
senorblanco@chromium.orgd4db6572014-05-07 20:00:04 +0000371 for (int scale = 1; scale <= 2; ++scale) {
372 for (size_t i = 0; i < SK_ARRAY_COUNT(filters); ++i) {
373 tiledCanvas.clear(0);
374 untiledCanvas.clear(0);
375 SkPaint paint;
376 paint.setImageFilter(filters[i].fFilter);
377 paint.setTextSize(SkIntToScalar(height));
378 paint.setColor(SK_ColorWHITE);
379 SkString str;
380 const char* text = "ABC";
381 SkScalar ypos = SkIntToScalar(height);
382 untiledCanvas.save();
383 untiledCanvas.scale(SkIntToScalar(scale), SkIntToScalar(scale));
384 untiledCanvas.drawText(text, strlen(text), 0, ypos, paint);
385 untiledCanvas.restore();
386 for (int y = 0; y < height; y += tileSize) {
387 for (int x = 0; x < width; x += tileSize) {
388 tiledCanvas.save();
389 tiledCanvas.clipRect(SkRect::Make(SkIRect::MakeXYWH(x, y, tileSize, tileSize)));
390 tiledCanvas.scale(SkIntToScalar(scale), SkIntToScalar(scale));
391 tiledCanvas.drawText(text, strlen(text), 0, ypos, paint);
392 tiledCanvas.restore();
393 }
senorblanco@chromium.org0a5c2332014-04-29 15:20:39 +0000394 }
senorblanco@chromium.orgd4db6572014-05-07 20:00:04 +0000395 untiledCanvas.flush();
396 tiledCanvas.flush();
397 for (int y = 0; y < height; y++) {
398 int diffs = memcmp(untiledResult.getAddr32(0, y), tiledResult.getAddr32(0, y), untiledResult.rowBytes());
399 REPORTER_ASSERT_MESSAGE(reporter, !diffs, filters[i].fName);
400 if (diffs) {
401 break;
402 }
senorblanco@chromium.org0a5c2332014-04-29 15:20:39 +0000403 }
404 }
405 }
406
407 for (size_t i = 0; i < SK_ARRAY_COUNT(filters); ++i) {
408 SkSafeUnref(filters[i].fFilter);
409 }
410}
411
senorblanco@chromium.org91957432014-05-01 14:03:41 +0000412DEF_TEST(ImageFilterMatrixConvolution, reporter) {
413 // Check that a 1x3 filter does not cause a spurious assert.
414 SkScalar kernel[3] = {
415 SkIntToScalar( 1), SkIntToScalar( 1), SkIntToScalar( 1),
416 };
417 SkISize kernelSize = SkISize::Make(1, 3);
418 SkScalar gain = SK_Scalar1, bias = 0;
419 SkIPoint kernelOffset = SkIPoint::Make(0, 0);
420
421 SkAutoTUnref<SkImageFilter> filter(
422 SkMatrixConvolutionImageFilter::Create(
423 kernelSize, kernel, gain, bias, kernelOffset,
424 SkMatrixConvolutionImageFilter::kRepeat_TileMode, false));
425
426 SkBitmap result;
427 int width = 16, height = 16;
428 result.allocN32Pixels(width, height);
429 SkCanvas canvas(result);
430 canvas.clear(0);
431
432 SkPaint paint;
433 paint.setImageFilter(filter);
434 SkRect rect = SkRect::Make(SkIRect::MakeWH(width, height));
435 canvas.drawRect(rect, paint);
436}
437
senorblanco@chromium.org8c7372b2014-05-02 19:13:11 +0000438DEF_TEST(ImageFilterMatrixConvolutionBorder, reporter) {
439 // Check that a filter with borders outside the target bounds
440 // does not crash.
441 SkScalar kernel[3] = {
442 0, 0, 0,
443 };
444 SkISize kernelSize = SkISize::Make(3, 1);
445 SkScalar gain = SK_Scalar1, bias = 0;
446 SkIPoint kernelOffset = SkIPoint::Make(2, 0);
447
448 SkAutoTUnref<SkImageFilter> filter(
449 SkMatrixConvolutionImageFilter::Create(
450 kernelSize, kernel, gain, bias, kernelOffset,
451 SkMatrixConvolutionImageFilter::kClamp_TileMode, true));
452
453 SkBitmap result;
454
455 int width = 10, height = 10;
456 result.allocN32Pixels(width, height);
457 SkCanvas canvas(result);
458 canvas.clear(0);
459
460 SkPaint filterPaint;
461 filterPaint.setImageFilter(filter);
462 SkRect bounds = SkRect::MakeWH(1, 10);
463 SkRect rect = SkRect::Make(SkIRect::MakeWH(width, height));
464 SkPaint rectPaint;
465 canvas.saveLayer(&bounds, &filterPaint);
466 canvas.drawRect(rect, rectPaint);
467 canvas.restore();
468}
469
senorblanco@chromium.orgaba651c2014-02-03 22:22:16 +0000470DEF_TEST(ImageFilterCropRect, reporter) {
471 SkBitmap temp;
mike@reedtribe.orgdeee4962014-02-13 14:41:43 +0000472 temp.allocN32Pixels(100, 100);
senorblanco@chromium.orgaba651c2014-02-03 22:22:16 +0000473 SkBitmapDevice device(temp);
474 test_crop_rects(&device, reporter);
475}
476
tfarina9ea53f92014-06-24 06:50:39 -0700477DEF_TEST(ImageFilterMatrix, reporter) {
senorblanco@chromium.org5251e2b2014-02-05 22:36:31 +0000478 SkBitmap temp;
mike@reedtribe.orgdeee4962014-02-13 14:41:43 +0000479 temp.allocN32Pixels(100, 100);
senorblanco@chromium.org5251e2b2014-02-05 22:36:31 +0000480 SkBitmapDevice device(temp);
481 SkCanvas canvas(&device);
482 canvas.scale(SkIntToScalar(2), SkIntToScalar(2));
483
484 SkMatrix expectedMatrix = canvas.getTotalMatrix();
485
commit-bot@chromium.org5fb2ce32014-04-17 23:35:06 +0000486 SkRTreeFactory factory;
487 SkPictureRecorder recorder;
488 SkCanvas* recordingCanvas = recorder.beginRecording(100, 100, &factory, 0);
senorblanco@chromium.org5251e2b2014-02-05 22:36:31 +0000489
490 SkPaint paint;
491 SkAutoTUnref<MatrixTestImageFilter> imageFilter(
492 new MatrixTestImageFilter(reporter, expectedMatrix));
493 paint.setImageFilter(imageFilter.get());
commit-bot@chromium.org091a5942014-04-18 14:19:31 +0000494 recordingCanvas->saveLayer(NULL, &paint);
senorblanco@chromium.org5251e2b2014-02-05 22:36:31 +0000495 SkPaint solidPaint;
496 solidPaint.setColor(0xFFFFFFFF);
497 recordingCanvas->save();
498 recordingCanvas->scale(SkIntToScalar(10), SkIntToScalar(10));
499 recordingCanvas->drawRect(SkRect::Make(SkIRect::MakeWH(100, 100)), solidPaint);
500 recordingCanvas->restore(); // scale
501 recordingCanvas->restore(); // saveLayer
robertphillips@google.com84b18c72014-04-13 19:09:42 +0000502 SkAutoTUnref<SkPicture> picture(recorder.endRecording());
senorblanco@chromium.org5251e2b2014-02-05 22:36:31 +0000503
robertphillips9b14f262014-06-04 05:40:44 -0700504 canvas.drawPicture(picture);
senorblanco@chromium.org5251e2b2014-02-05 22:36:31 +0000505}
506
tfarina9ea53f92014-06-24 06:50:39 -0700507DEF_TEST(ImageFilterPictureImageFilter, reporter) {
senorblanco@chromium.org97f5fc62014-05-30 20:50:56 +0000508 SkRTreeFactory factory;
509 SkPictureRecorder recorder;
510 SkCanvas* recordingCanvas = recorder.beginRecording(1, 1, &factory, 0);
511
512 // Create an SkPicture which simply draws a green 1x1 rectangle.
513 SkPaint greenPaint;
514 greenPaint.setColor(SK_ColorGREEN);
515 recordingCanvas->drawRect(SkRect::Make(SkIRect::MakeWH(1, 1)), greenPaint);
516 SkAutoTUnref<SkPicture> picture(recorder.endRecording());
517
518 // Wrap that SkPicture in an SkPictureImageFilter.
519 SkAutoTUnref<SkImageFilter> imageFilter(
520 SkPictureImageFilter::Create(picture.get()));
521
522 // Check that SkPictureImageFilter successfully serializes its contained
523 // SkPicture when not in cross-process mode.
524 SkPaint paint;
525 paint.setImageFilter(imageFilter.get());
526 SkPictureRecorder outerRecorder;
527 SkCanvas* outerCanvas = outerRecorder.beginRecording(1, 1, &factory, 0);
528 SkPaint redPaintWithFilter;
529 redPaintWithFilter.setColor(SK_ColorRED);
530 redPaintWithFilter.setImageFilter(imageFilter.get());
531 outerCanvas->drawRect(SkRect::Make(SkIRect::MakeWH(1, 1)), redPaintWithFilter);
532 SkAutoTUnref<SkPicture> outerPicture(outerRecorder.endRecording());
533
534 SkBitmap bitmap;
535 bitmap.allocN32Pixels(1, 1);
536 SkBitmapDevice device(bitmap);
537 SkCanvas canvas(&device);
538
539 // The result here should be green, since the filter replaces the primitive's red interior.
540 canvas.clear(0x0);
robertphillips9b14f262014-06-04 05:40:44 -0700541 canvas.drawPicture(outerPicture);
senorblanco@chromium.org97f5fc62014-05-30 20:50:56 +0000542 uint32_t pixel = *bitmap.getAddr32(0, 0);
543 REPORTER_ASSERT(reporter, pixel == SK_ColorGREEN);
544
545 // Check that, for now, SkPictureImageFilter does not serialize or
546 // deserialize its contained picture when the filter is serialized
547 // cross-process. Do this by "laundering" it through SkValidatingReadBuffer.
548 SkAutoTUnref<SkData> data(SkValidatingSerializeFlattenable(imageFilter.get()));
549 SkAutoTUnref<SkFlattenable> flattenable(SkValidatingDeserializeFlattenable(
550 data->data(), data->size(), SkImageFilter::GetFlattenableType()));
551 SkImageFilter* unflattenedFilter = static_cast<SkImageFilter*>(flattenable.get());
552
553 redPaintWithFilter.setImageFilter(unflattenedFilter);
554 SkPictureRecorder crossProcessRecorder;
555 SkCanvas* crossProcessCanvas = crossProcessRecorder.beginRecording(1, 1, &factory, 0);
556 crossProcessCanvas->drawRect(SkRect::Make(SkIRect::MakeWH(1, 1)), redPaintWithFilter);
557 SkAutoTUnref<SkPicture> crossProcessPicture(crossProcessRecorder.endRecording());
558
559 canvas.clear(0x0);
robertphillips9b14f262014-06-04 05:40:44 -0700560 canvas.drawPicture(crossProcessPicture);
senorblanco@chromium.org97f5fc62014-05-30 20:50:56 +0000561 pixel = *bitmap.getAddr32(0, 0);
562 // The result here should not be green, since the filter draws nothing.
563 REPORTER_ASSERT(reporter, pixel != SK_ColorGREEN);
564}
565
tfarina9ea53f92014-06-24 06:50:39 -0700566DEF_TEST(ImageFilterEmptySaveLayer, reporter) {
senorblanco@chromium.org68250c82014-05-06 22:52:55 +0000567 // Even when there's an empty saveLayer()/restore(), ensure that an image
568 // filter or color filter which affects transparent black still draws.
569
570 SkBitmap bitmap;
571 bitmap.allocN32Pixels(10, 10);
572 SkBitmapDevice device(bitmap);
573 SkCanvas canvas(&device);
574
575 SkRTreeFactory factory;
576 SkPictureRecorder recorder;
577
578 SkAutoTUnref<SkColorFilter> green(
579 SkColorFilter::CreateModeFilter(SK_ColorGREEN, SkXfermode::kSrc_Mode));
580 SkAutoTUnref<SkColorFilterImageFilter> imageFilter(
581 SkColorFilterImageFilter::Create(green.get()));
582 SkPaint imageFilterPaint;
583 imageFilterPaint.setImageFilter(imageFilter.get());
584 SkPaint colorFilterPaint;
585 colorFilterPaint.setColorFilter(green.get());
586
587 SkRect bounds = SkRect::MakeWH(10, 10);
588
589 SkCanvas* recordingCanvas = recorder.beginRecording(10, 10, &factory, 0);
590 recordingCanvas->saveLayer(&bounds, &imageFilterPaint);
591 recordingCanvas->restore();
592 SkAutoTUnref<SkPicture> picture(recorder.endRecording());
593
594 canvas.clear(0);
robertphillips9b14f262014-06-04 05:40:44 -0700595 canvas.drawPicture(picture);
senorblanco@chromium.org68250c82014-05-06 22:52:55 +0000596 uint32_t pixel = *bitmap.getAddr32(0, 0);
597 REPORTER_ASSERT(reporter, pixel == SK_ColorGREEN);
598
599 recordingCanvas = recorder.beginRecording(10, 10, &factory, 0);
600 recordingCanvas->saveLayer(NULL, &imageFilterPaint);
601 recordingCanvas->restore();
602 SkAutoTUnref<SkPicture> picture2(recorder.endRecording());
603
604 canvas.clear(0);
robertphillips9b14f262014-06-04 05:40:44 -0700605 canvas.drawPicture(picture2);
senorblanco@chromium.org68250c82014-05-06 22:52:55 +0000606 pixel = *bitmap.getAddr32(0, 0);
607 REPORTER_ASSERT(reporter, pixel == SK_ColorGREEN);
608
609 recordingCanvas = recorder.beginRecording(10, 10, &factory, 0);
610 recordingCanvas->saveLayer(&bounds, &colorFilterPaint);
611 recordingCanvas->restore();
612 SkAutoTUnref<SkPicture> picture3(recorder.endRecording());
613
614 canvas.clear(0);
robertphillips9b14f262014-06-04 05:40:44 -0700615 canvas.drawPicture(picture3);
senorblanco@chromium.org68250c82014-05-06 22:52:55 +0000616 pixel = *bitmap.getAddr32(0, 0);
617 REPORTER_ASSERT(reporter, pixel == SK_ColorGREEN);
618}
619
senorblanco@chromium.org28ae55d2014-03-24 21:32:28 +0000620static void test_huge_blur(SkBaseDevice* device, skiatest::Reporter* reporter) {
senorblanco@chromium.org09843fd2014-03-24 20:50:59 +0000621 SkCanvas canvas(device);
622
623 SkBitmap bitmap;
624 bitmap.allocN32Pixels(100, 100);
625 bitmap.eraseARGB(0, 0, 0, 0);
626
627 // Check that a blur with an insane radius does not crash or assert.
628 SkAutoTUnref<SkImageFilter> blur(SkBlurImageFilter::Create(SkIntToScalar(1<<30), SkIntToScalar(1<<30)));
629
630 SkPaint paint;
631 paint.setImageFilter(blur);
632 canvas.drawSprite(bitmap, 0, 0, &paint);
633}
634
635DEF_TEST(HugeBlurImageFilter, reporter) {
636 SkBitmap temp;
637 temp.allocN32Pixels(100, 100);
638 SkBitmapDevice device(temp);
639 test_huge_blur(&device, reporter);
640}
641
senorblanco@chromium.orgee845ae2014-04-01 19:15:23 +0000642static void test_xfermode_cropped_input(SkBaseDevice* device, skiatest::Reporter* reporter) {
643 SkCanvas canvas(device);
644 canvas.clear(0);
645
646 SkBitmap bitmap;
647 bitmap.allocN32Pixels(1, 1);
648 bitmap.eraseARGB(255, 255, 255, 255);
649
650 SkAutoTUnref<SkColorFilter> green(
651 SkColorFilter::CreateModeFilter(SK_ColorGREEN, SkXfermode::kSrcIn_Mode));
652 SkAutoTUnref<SkColorFilterImageFilter> greenFilter(
653 SkColorFilterImageFilter::Create(green.get()));
654 SkImageFilter::CropRect cropRect(SkRect::MakeEmpty());
655 SkAutoTUnref<SkColorFilterImageFilter> croppedOut(
656 SkColorFilterImageFilter::Create(green.get(), NULL, &cropRect));
657
658 // Check that an xfermode image filter whose input has been cropped out still draws the other
659 // input. Also check that drawing with both inputs cropped out doesn't cause a GPU warning.
660 SkXfermode* mode = SkXfermode::Create(SkXfermode::kSrcOver_Mode);
661 SkAutoTUnref<SkImageFilter> xfermodeNoFg(
662 SkXfermodeImageFilter::Create(mode, greenFilter, croppedOut));
663 SkAutoTUnref<SkImageFilter> xfermodeNoBg(
664 SkXfermodeImageFilter::Create(mode, croppedOut, greenFilter));
665 SkAutoTUnref<SkImageFilter> xfermodeNoFgNoBg(
666 SkXfermodeImageFilter::Create(mode, croppedOut, croppedOut));
667
668 SkPaint paint;
669 paint.setImageFilter(xfermodeNoFg);
670 canvas.drawSprite(bitmap, 0, 0, &paint);
671
672 uint32_t pixel;
673 SkImageInfo info = SkImageInfo::MakeN32Premul(1, 1);
674 canvas.readPixels(info, &pixel, 4, 0, 0);
675 REPORTER_ASSERT(reporter, pixel == SK_ColorGREEN);
676
677 paint.setImageFilter(xfermodeNoBg);
678 canvas.drawSprite(bitmap, 0, 0, &paint);
679 canvas.readPixels(info, &pixel, 4, 0, 0);
680 REPORTER_ASSERT(reporter, pixel == SK_ColorGREEN);
681
682 paint.setImageFilter(xfermodeNoFgNoBg);
683 canvas.drawSprite(bitmap, 0, 0, &paint);
684 canvas.readPixels(info, &pixel, 4, 0, 0);
685 REPORTER_ASSERT(reporter, pixel == SK_ColorGREEN);
686}
687
senorblanco@chromium.orgd5424a42014-04-02 19:20:05 +0000688DEF_TEST(ImageFilterNestedSaveLayer, reporter) {
689 SkBitmap temp;
690 temp.allocN32Pixels(50, 50);
691 SkBitmapDevice device(temp);
692 SkCanvas canvas(&device);
693 canvas.clear(0x0);
694
695 SkBitmap bitmap;
696 bitmap.allocN32Pixels(10, 10);
697 bitmap.eraseColor(SK_ColorGREEN);
698
699 SkMatrix matrix;
700 matrix.setScale(SkIntToScalar(2), SkIntToScalar(2));
701 matrix.postTranslate(SkIntToScalar(-20), SkIntToScalar(-20));
702 SkAutoTUnref<SkImageFilter> matrixFilter(
703 SkMatrixImageFilter::Create(matrix, SkPaint::kLow_FilterLevel));
704
705 // Test that saveLayer() with a filter nested inside another saveLayer() applies the
706 // correct offset to the filter matrix.
707 SkRect bounds1 = SkRect::MakeXYWH(10, 10, 30, 30);
708 canvas.saveLayer(&bounds1, NULL);
709 SkPaint filterPaint;
710 filterPaint.setImageFilter(matrixFilter);
711 SkRect bounds2 = SkRect::MakeXYWH(20, 20, 10, 10);
712 canvas.saveLayer(&bounds2, &filterPaint);
713 SkPaint greenPaint;
714 greenPaint.setColor(SK_ColorGREEN);
715 canvas.drawRect(bounds2, greenPaint);
716 canvas.restore();
717 canvas.restore();
718 SkPaint strokePaint;
719 strokePaint.setStyle(SkPaint::kStroke_Style);
720 strokePaint.setColor(SK_ColorRED);
721
722 SkImageInfo info = SkImageInfo::MakeN32Premul(1, 1);
723 uint32_t pixel;
724 canvas.readPixels(info, &pixel, 4, 25, 25);
725 REPORTER_ASSERT(reporter, pixel == SK_ColorGREEN);
726
727 // Test that drawSprite() with a filter nested inside a saveLayer() applies the
728 // correct offset to the filter matrix.
729 canvas.clear(0x0);
730 canvas.readPixels(info, &pixel, 4, 25, 25);
731 canvas.saveLayer(&bounds1, NULL);
732 canvas.drawSprite(bitmap, 20, 20, &filterPaint);
733 canvas.restore();
734
735 canvas.readPixels(info, &pixel, 4, 25, 25);
736 REPORTER_ASSERT(reporter, pixel == SK_ColorGREEN);
737}
738
senorblanco@chromium.orgee845ae2014-04-01 19:15:23 +0000739DEF_TEST(XfermodeImageFilterCroppedInput, reporter) {
740 SkBitmap temp;
741 temp.allocN32Pixels(100, 100);
742 SkBitmapDevice device(temp);
743 test_xfermode_cropped_input(&device, reporter);
744}
senorblanco@chromium.org09843fd2014-03-24 20:50:59 +0000745
senorblanco@chromium.org58d14662014-02-03 22:36:39 +0000746#if SK_SUPPORT_GPU
senorblanco@chromium.orgaba651c2014-02-03 22:22:16 +0000747DEF_GPUTEST(ImageFilterCropRectGPU, reporter, factory) {
748 GrContext* context = factory->get(static_cast<GrContextFactory::GLContextType>(0));
commit-bot@chromium.org15a14052014-02-16 00:59:25 +0000749 SkAutoTUnref<SkGpuDevice> device(SkGpuDevice::Create(context,
750 SkImageInfo::MakeN32Premul(100, 100),
751 0));
752 test_crop_rects(device, reporter);
tfarina@chromium.org9f9d5822013-12-18 22:15:12 +0000753}
senorblanco@chromium.org09843fd2014-03-24 20:50:59 +0000754
755DEF_GPUTEST(HugeBlurImageFilterGPU, reporter, factory) {
756 GrContext* context = factory->get(static_cast<GrContextFactory::GLContextType>(0));
757 SkAutoTUnref<SkGpuDevice> device(SkGpuDevice::Create(context,
758 SkImageInfo::MakeN32Premul(100, 100),
759 0));
760 test_huge_blur(device, reporter);
761}
senorblanco@chromium.orgee845ae2014-04-01 19:15:23 +0000762
763DEF_GPUTEST(XfermodeImageFilterCroppedInputGPU, reporter, factory) {
764 GrContext* context = factory->get(static_cast<GrContextFactory::GLContextType>(0));
765 SkAutoTUnref<SkGpuDevice> device(SkGpuDevice::Create(context,
766 SkImageInfo::MakeN32Premul(1, 1),
767 0));
768 test_xfermode_cropped_input(device, reporter);
769}
senorblanco@chromium.org58d14662014-02-03 22:36:39 +0000770#endif