1 /*
2 * Copyright 2018 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 "gm/gm.h"
9 #include "include/codec/SkEncodedOrigin.h"
10 #include "include/core/SkBlurTypes.h"
11 #include "include/core/SkCanvas.h"
12 #include "include/core/SkFont.h"
13 #include "include/core/SkImage.h"
14 #include "include/core/SkMaskFilter.h"
15 #include "include/core/SkRefCnt.h"
16 #include "include/core/SkSize.h"
17 #include "include/core/SkString.h"
18 #include "include/core/SkSurface.h"
19 #include "src/codec/SkCodecImageGenerator.h"
20 #include "tools/DecodeUtils.h"
21 #include "tools/EncodeUtils.h"
22 #include "tools/Resources.h"
23 #include "tools/ToolUtils.h"
24 #include "tools/fonts/FontToolUtils.h"
25
26 static constexpr int kImgW = 100;
27 static constexpr int kImgH = 80;
28
29 /**
30 This function was used to create the images used by these test. It saves them as PNGs (so they
31 are lossless). Then the following bash script was used to create the oriented JPGs with
32 imagemagick and exiftool:
33 #!/bin/bash
34
35 for s in 444 422 420 440 411 410; do
36 for i in {1..8}; do
37 magick convert $i.png -sampling-factor ${s:0:1}:${s:1:1}:${s:2:1} $i\_$s.jpg;
38 exiftool -orientation=$i -n -m -overwrite_original $i\_$s.jpg;
39 done
40 done
41
42 */
make_images()43 static void make_images() {
44 for (int i = 1; i <= 8; ++i) {
45 SkISize size{kImgW, kImgH};
46 SkEncodedOrigin origin = static_cast<SkEncodedOrigin>(i);
47 // We apply the inverse transformation to the PNG we generate, convert the PNG to a
48 // a JPEG using magick, then modify the JPEG's tag using exiftool (without modifying the
49 // stored JPEG data).
50 if (origin >= kLeftTop_SkEncodedOrigin) {
51 // The last four SkEncodedOrigin values involve 90 degree rotations
52 using std::swap;
53 swap(size.fWidth, size.fHeight);
54 }
55 using std::swap;
56 auto surf = SkSurfaces::Raster(
57 SkImageInfo::Make(size, kRGBA_8888_SkColorType, kPremul_SkAlphaType));
58 auto* canvas = surf->getCanvas();
59 SkMatrix m = SkEncodedOriginToMatrix(origin, kImgW, kImgH);
60 SkAssertResult(m.invert(&m));
61 canvas->concat(m);
62 canvas->clear(SK_ColorBLACK);
63 SkPaint paint;
64 paint.setColor(SK_ColorRED);
65 SkScalar midX = kImgW / 2.f;
66 SkScalar midY = kImgH / 2.f;
67 SkScalar w = midX - 1;
68 SkScalar h = midY - 1;
69 canvas->drawRect(SkRect::MakeXYWH(1, 1, w, h), paint);
70 paint.setColor(SK_ColorBLUE);
71 canvas->drawRect(SkRect::MakeXYWH(midX, 1, w, h), paint);
72 paint.setColor(SK_ColorGREEN);
73 canvas->drawRect(SkRect::MakeXYWH(1, midY, w, h), paint);
74 paint.setColor(SK_ColorYELLOW);
75 canvas->drawRect(SkRect::MakeXYWH(midX, midY, w, h), paint);
76 SkFont font(ToolUtils::DefaultPortableTypeface(), kImgH / 4.f);
77
78 SkPaint blurPaint;
79 blurPaint.setMaskFilter(SkMaskFilter::MakeBlur(kNormal_SkBlurStyle, .75f));
80 blurPaint.setColor(SK_ColorBLACK);
81 paint.setColor(SK_ColorWHITE);
82
83 auto drawLabel = [&](const char* string, SkScalar x, SkScalar y) {
84 canvas->save();
85 canvas->translate(1, 1);
86 canvas->drawString(string, x, y, font, blurPaint);
87 canvas->restore();
88 canvas->drawString(string, x, y, font, paint);
89 };
90
91 auto measure = [&font](const char* text) {
92 SkRect bounds;
93 font.measureText(text, strlen(text), SkTextEncoding::kUTF8, &bounds);
94 return bounds;
95 };
96
97 static constexpr SkScalar kPad = 3.f;
98 SkRect bounds;
99
100 bounds = measure("top");
101 drawLabel("top", midX - bounds.centerX(), -bounds.top() + kPad);
102
103 bounds = measure("bottom");
104 drawLabel("bottom", midX - bounds.centerX(), kImgH - kPad - bounds.bottom());
105
106 // It looks weird if "left" and "right" and the number at the center aren't vertically
107 // aligned.
108 SkScalar baseY = midY - measure("leftright").centerY();
109 bounds = measure("left");
110 drawLabel("left", kPad - bounds.left(), baseY);
111
112 bounds = measure("right");
113 drawLabel("right", kImgW - kPad - bounds.right(), baseY);
114
115 SkString num = SkStringPrintf("%d", i);
116 bounds = measure(num.c_str());
117 drawLabel(num.c_str(), midX - bounds.centerX(), baseY);
118 num.append(".png");
119 SkPixmap pm;
120 surf->makeImageSnapshot()->peekPixels(&pm);
121 ToolUtils::EncodeImageToPngFile(num.c_str(), pm);
122 }
123 }
124
125 // This gm draws 8 images that are mostly the same when respecting the
126 // EXIF orientation tag. Each one has four quadrants (red, blue, green,
127 // yellow), and labels on the left, top, right and bottom. The only
128 // visual difference is a number in the middle corresponding to the
129 // EXIF tag for that image's jpg file.
draw(SkCanvas * canvas,const char * suffix)130 static void draw(SkCanvas* canvas, const char* suffix) {
131 // Avoid unused function warning.
132 if ((false)) {
133 make_images();
134 }
135 canvas->save();
136 for (char i = '1'; i <= '8'; i++) {
137 SkString path = SkStringPrintf("images/orientation/%c%s.jpg", i, suffix);
138 auto image = ToolUtils::GetResourceAsImage(path.c_str());
139 if (!image) {
140 continue;
141 }
142 canvas->drawImage(image, 0, 0);
143 if ('4' == i) {
144 canvas->restore();
145 canvas->translate(0, image->height());
146 } else {
147 canvas->translate(image->width(), 0);
148 }
149 }
150 }
151
152 #define MAKE_GM(subsample) DEF_SIMPLE_GM(orientation_##subsample, canvas, 4*kImgW, 2*kImgH) { \
153 draw(canvas, "_" #subsample); \
154 }
155
156 MAKE_GM(410)
157 MAKE_GM(411)
158 MAKE_GM(420)
159 MAKE_GM(422)
160 MAKE_GM(440)
161 MAKE_GM(444)
162
163 // This GM demonstrates that the default SkImageGenerator respects the orientation flag.
164 DEF_SIMPLE_GM(respect_orientation_jpeg, canvas, 4*kImgW, 2*kImgH) {
165 canvas->save();
166 for (char i = '1'; i <= '8'; i++) {
167 SkString path = SkStringPrintf("images/orientation/%c_444.jpg", i);
168 // Get the image as data
169 sk_sp<SkData> data(GetResourceAsData(path.c_str()));
170 sk_sp<SkImage> image = SkImages::DeferredFromGenerator(
171 SkCodecImageGenerator::MakeFromEncodedCodec(data));
172 if (!image) {
173 continue;
174 }
175 canvas->drawImage(image, 0, 0);
176 if ('4' == i || '8' == i) {
177 canvas->restore();
178 canvas->translate(0, image->height());
179 } else {
180 canvas->translate(image->width(), 0);
181 }
182 }
183 }
184