1c69809745e6496564639e42ef998ad39adf7dfb8bsalomon@google.com/*
2c69809745e6496564639e42ef998ad39adf7dfb8bsalomon@google.com * Copyright 2011 Google Inc.
3c69809745e6496564639e42ef998ad39adf7dfb8bsalomon@google.com *
4c69809745e6496564639e42ef998ad39adf7dfb8bsalomon@google.com * Use of this source code is governed by a BSD-style license that can be
5c69809745e6496564639e42ef998ad39adf7dfb8bsalomon@google.com * found in the LICENSE file.
6c69809745e6496564639e42ef998ad39adf7dfb8bsalomon@google.com */
7c69809745e6496564639e42ef998ad39adf7dfb8bsalomon@google.com
873672254a3e498081967d00d27b17ada443e2ab2robertphillips@google.com#include "SkBitmapDevice.h"
9c69809745e6496564639e42ef998ad39adf7dfb8bsalomon@google.com#include "SkCanvas.h"
10cf8fb1f6f03fc77f9927564f9ef9abeeeec508d2bsalomon@google.com#include "SkColorPriv.h"
114b163ed2c22facbe8891616874ae07ba7827d9c9reed@google.com#include "SkMathPriv.h"
12c69809745e6496564639e42ef998ad39adf7dfb8bsalomon@google.com#include "SkRegion.h"
134ee16bfaedb14aff8cf102f1f0722ff2529a9699tfarina@chromium.org#include "Test.h"
144ee16bfaedb14aff8cf102f1f0722ff2529a9699tfarina@chromium.org
15cf8fb1f6f03fc77f9927564f9ef9abeeeec508d2bsalomon@google.com#if SK_SUPPORT_GPU
1667b915de99e6b89d476907930ac8c27afb64d10ebsalomon@google.com#include "GrContextFactory.h"
178f6884aab8aecd7657cf3f9cdbc682f0deca29c5tfarina@chromium.org#include "SkGpuDevice.h"
18cf8fb1f6f03fc77f9927564f9ef9abeeeec508d2bsalomon@google.com#endif
19c69809745e6496564639e42ef998ad39adf7dfb8bsalomon@google.com
20c69809745e6496564639e42ef998ad39adf7dfb8bsalomon@google.comstatic const int DEV_W = 100, DEV_H = 100;
21c69809745e6496564639e42ef998ad39adf7dfb8bsalomon@google.comstatic const SkIRect DEV_RECT = SkIRect::MakeWH(DEV_W, DEV_H);
22d6176b0dcacb124539e0cfd051e6d93a9782f020rmistry@google.comstatic const SkRect DEV_RECT_S = SkRect::MakeWH(DEV_W * SK_Scalar1,
23c69809745e6496564639e42ef998ad39adf7dfb8bsalomon@google.com                                                DEV_H * SK_Scalar1);
24c69809745e6496564639e42ef998ad39adf7dfb8bsalomon@google.com
25ddf94cf108ae430877f009bd67b9070341426947commit-bot@chromium.orgstatic SkPMColor getCanvasColor(int x, int y) {
26c69809745e6496564639e42ef998ad39adf7dfb8bsalomon@google.com    SkASSERT(x >= 0 && x < DEV_W);
27c69809745e6496564639e42ef998ad39adf7dfb8bsalomon@google.com    SkASSERT(y >= 0 && y < DEV_H);
286850eab42ba4c2a7033a99824b02a2846ce0ef2absalomon@google.com
296850eab42ba4c2a7033a99824b02a2846ce0ef2absalomon@google.com    U8CPU r = x;
306850eab42ba4c2a7033a99824b02a2846ce0ef2absalomon@google.com    U8CPU g = y;
316850eab42ba4c2a7033a99824b02a2846ce0ef2absalomon@google.com    U8CPU b = 0xc;
326850eab42ba4c2a7033a99824b02a2846ce0ef2absalomon@google.com
336850eab42ba4c2a7033a99824b02a2846ce0ef2absalomon@google.com    U8CPU a = 0xff;
34c43649962221c348d656d425a3fa9b29c78231d4bsalomon@google.com    switch ((x+y) % 5) {
356850eab42ba4c2a7033a99824b02a2846ce0ef2absalomon@google.com        case 0:
366850eab42ba4c2a7033a99824b02a2846ce0ef2absalomon@google.com            a = 0xff;
376850eab42ba4c2a7033a99824b02a2846ce0ef2absalomon@google.com            break;
386850eab42ba4c2a7033a99824b02a2846ce0ef2absalomon@google.com        case 1:
396850eab42ba4c2a7033a99824b02a2846ce0ef2absalomon@google.com            a = 0x80;
406850eab42ba4c2a7033a99824b02a2846ce0ef2absalomon@google.com            break;
416850eab42ba4c2a7033a99824b02a2846ce0ef2absalomon@google.com        case 2:
426850eab42ba4c2a7033a99824b02a2846ce0ef2absalomon@google.com            a = 0xCC;
436850eab42ba4c2a7033a99824b02a2846ce0ef2absalomon@google.com            break;
446850eab42ba4c2a7033a99824b02a2846ce0ef2absalomon@google.com        case 4:
456850eab42ba4c2a7033a99824b02a2846ce0ef2absalomon@google.com            a = 0x01;
466850eab42ba4c2a7033a99824b02a2846ce0ef2absalomon@google.com            break;
476850eab42ba4c2a7033a99824b02a2846ce0ef2absalomon@google.com        case 3:
486850eab42ba4c2a7033a99824b02a2846ce0ef2absalomon@google.com            a = 0x00;
496850eab42ba4c2a7033a99824b02a2846ce0ef2absalomon@google.com            break;
506850eab42ba4c2a7033a99824b02a2846ce0ef2absalomon@google.com    }
516850eab42ba4c2a7033a99824b02a2846ce0ef2absalomon@google.com    return SkPremultiplyARGBInline(a, r, g, b);
52c69809745e6496564639e42ef998ad39adf7dfb8bsalomon@google.com}
53d6176b0dcacb124539e0cfd051e6d93a9782f020rmistry@google.com
54ddf94cf108ae430877f009bd67b9070341426947commit-bot@chromium.orgstatic SkPMColor getBitmapColor(int x, int y, int w) {
55c69809745e6496564639e42ef998ad39adf7dfb8bsalomon@google.com    int n = y * w + x;
566850eab42ba4c2a7033a99824b02a2846ce0ef2absalomon@google.com
57c69809745e6496564639e42ef998ad39adf7dfb8bsalomon@google.com    U8CPU b = n & 0xff;
58c69809745e6496564639e42ef998ad39adf7dfb8bsalomon@google.com    U8CPU g = (n >> 8) & 0xff;
59c69809745e6496564639e42ef998ad39adf7dfb8bsalomon@google.com    U8CPU r = (n >> 16) & 0xff;
60c69809745e6496564639e42ef998ad39adf7dfb8bsalomon@google.com    return SkPackARGB32(0xff, r, g , b);
61c69809745e6496564639e42ef998ad39adf7dfb8bsalomon@google.com}
62c69809745e6496564639e42ef998ad39adf7dfb8bsalomon@google.com
63a713f9c6f6a06d216d53e268b9c691941053dabfcommit-bot@chromium.orgstatic SkPMColor convertToPMColor(SkColorType ct, SkAlphaType at, const uint32_t* addr,
64a713f9c6f6a06d216d53e268b9c691941053dabfcommit-bot@chromium.org                                  bool* doUnpremul) {
65a713f9c6f6a06d216d53e268b9c691941053dabfcommit-bot@chromium.org    *doUnpremul = (kUnpremul_SkAlphaType == at);
66a713f9c6f6a06d216d53e268b9c691941053dabfcommit-bot@chromium.org
67a713f9c6f6a06d216d53e268b9c691941053dabfcommit-bot@chromium.org    const uint8_t* c = reinterpret_cast<const uint8_t*>(addr);
686850eab42ba4c2a7033a99824b02a2846ce0ef2absalomon@google.com    U8CPU a,r,g,b;
69a713f9c6f6a06d216d53e268b9c691941053dabfcommit-bot@chromium.org    switch (ct) {
70a713f9c6f6a06d216d53e268b9c691941053dabfcommit-bot@chromium.org        case kBGRA_8888_SkColorType:
716850eab42ba4c2a7033a99824b02a2846ce0ef2absalomon@google.com            b = static_cast<U8CPU>(c[0]);
72a713f9c6f6a06d216d53e268b9c691941053dabfcommit-bot@chromium.org            g = static_cast<U8CPU>(c[1]);
73a713f9c6f6a06d216d53e268b9c691941053dabfcommit-bot@chromium.org            r = static_cast<U8CPU>(c[2]);
746850eab42ba4c2a7033a99824b02a2846ce0ef2absalomon@google.com            a = static_cast<U8CPU>(c[3]);
75a713f9c6f6a06d216d53e268b9c691941053dabfcommit-bot@chromium.org            break;
76a713f9c6f6a06d216d53e268b9c691941053dabfcommit-bot@chromium.org        case kRGBA_8888_SkColorType:
776850eab42ba4c2a7033a99824b02a2846ce0ef2absalomon@google.com            r = static_cast<U8CPU>(c[0]);
786850eab42ba4c2a7033a99824b02a2846ce0ef2absalomon@google.com            g = static_cast<U8CPU>(c[1]);
796850eab42ba4c2a7033a99824b02a2846ce0ef2absalomon@google.com            b = static_cast<U8CPU>(c[2]);
80a713f9c6f6a06d216d53e268b9c691941053dabfcommit-bot@chromium.org            a = static_cast<U8CPU>(c[3]);
816850eab42ba4c2a7033a99824b02a2846ce0ef2absalomon@google.com            break;
82ccaa002dd81a6a8bd5acb7a2fa69a2437873c1fdbsalomon@google.com        default:
83a713f9c6f6a06d216d53e268b9c691941053dabfcommit-bot@chromium.org            SkDEBUGFAIL("Unexpected colortype");
84ccaa002dd81a6a8bd5acb7a2fa69a2437873c1fdbsalomon@google.com            return 0;
856850eab42ba4c2a7033a99824b02a2846ce0ef2absalomon@google.com    }
86a713f9c6f6a06d216d53e268b9c691941053dabfcommit-bot@chromium.org
87a713f9c6f6a06d216d53e268b9c691941053dabfcommit-bot@chromium.org    if (*doUnpremul) {
886850eab42ba4c2a7033a99824b02a2846ce0ef2absalomon@google.com        r = SkMulDiv255Ceiling(r, a);
896850eab42ba4c2a7033a99824b02a2846ce0ef2absalomon@google.com        g = SkMulDiv255Ceiling(g, a);
906850eab42ba4c2a7033a99824b02a2846ce0ef2absalomon@google.com        b = SkMulDiv255Ceiling(b, a);
916850eab42ba4c2a7033a99824b02a2846ce0ef2absalomon@google.com    }
926850eab42ba4c2a7033a99824b02a2846ce0ef2absalomon@google.com    return SkPackARGB32(a, r, g, b);
936850eab42ba4c2a7033a99824b02a2846ce0ef2absalomon@google.com}
946850eab42ba4c2a7033a99824b02a2846ce0ef2absalomon@google.com
95ddf94cf108ae430877f009bd67b9070341426947commit-bot@chromium.orgstatic void fillCanvas(SkCanvas* canvas) {
96c69809745e6496564639e42ef998ad39adf7dfb8bsalomon@google.com    static SkBitmap bmp;
97c69809745e6496564639e42ef998ad39adf7dfb8bsalomon@google.com    if (bmp.isNull()) {
98fa9e5fa42a555712fb7a29d08d2ae2bdef0ed68ecommit-bot@chromium.org        SkDEBUGCODE(bool alloc =) bmp.allocN32Pixels(DEV_W, DEV_H);
99c69809745e6496564639e42ef998ad39adf7dfb8bsalomon@google.com        SkASSERT(alloc);
100c69809745e6496564639e42ef998ad39adf7dfb8bsalomon@google.com        SkAutoLockPixels alp(bmp);
101c69809745e6496564639e42ef998ad39adf7dfb8bsalomon@google.com        intptr_t pixels = reinterpret_cast<intptr_t>(bmp.getPixels());
102c69809745e6496564639e42ef998ad39adf7dfb8bsalomon@google.com        for (int y = 0; y < DEV_H; ++y) {
103c69809745e6496564639e42ef998ad39adf7dfb8bsalomon@google.com            for (int x = 0; x < DEV_W; ++x) {
104c69809745e6496564639e42ef998ad39adf7dfb8bsalomon@google.com                SkPMColor* pixel = reinterpret_cast<SkPMColor*>(pixels + y * bmp.rowBytes() + x * bmp.bytesPerPixel());
105c69809745e6496564639e42ef998ad39adf7dfb8bsalomon@google.com                *pixel = getCanvasColor(x, y);
106c69809745e6496564639e42ef998ad39adf7dfb8bsalomon@google.com            }
107c69809745e6496564639e42ef998ad39adf7dfb8bsalomon@google.com        }
108c69809745e6496564639e42ef998ad39adf7dfb8bsalomon@google.com    }
109c69809745e6496564639e42ef998ad39adf7dfb8bsalomon@google.com    canvas->save();
110c69809745e6496564639e42ef998ad39adf7dfb8bsalomon@google.com    canvas->setMatrix(SkMatrix::I());
111c69809745e6496564639e42ef998ad39adf7dfb8bsalomon@google.com    canvas->clipRect(DEV_RECT_S, SkRegion::kReplace_Op);
112c69809745e6496564639e42ef998ad39adf7dfb8bsalomon@google.com    SkPaint paint;
113c69809745e6496564639e42ef998ad39adf7dfb8bsalomon@google.com    paint.setXfermodeMode(SkXfermode::kSrc_Mode);
114c69809745e6496564639e42ef998ad39adf7dfb8bsalomon@google.com    canvas->drawBitmap(bmp, 0, 0, &paint);
115c69809745e6496564639e42ef998ad39adf7dfb8bsalomon@google.com    canvas->restore();
116c69809745e6496564639e42ef998ad39adf7dfb8bsalomon@google.com}
117d6176b0dcacb124539e0cfd051e6d93a9782f020rmistry@google.com
118ddf94cf108ae430877f009bd67b9070341426947commit-bot@chromium.orgstatic void fillBitmap(SkBitmap* bitmap) {
119c69809745e6496564639e42ef998ad39adf7dfb8bsalomon@google.com    SkASSERT(bitmap->lockPixelsAreWritable());
120c69809745e6496564639e42ef998ad39adf7dfb8bsalomon@google.com    SkAutoLockPixels alp(*bitmap);
121c69809745e6496564639e42ef998ad39adf7dfb8bsalomon@google.com    int w = bitmap->width();
122c69809745e6496564639e42ef998ad39adf7dfb8bsalomon@google.com    int h = bitmap->height();
123c69809745e6496564639e42ef998ad39adf7dfb8bsalomon@google.com    intptr_t pixels = reinterpret_cast<intptr_t>(bitmap->getPixels());
124c69809745e6496564639e42ef998ad39adf7dfb8bsalomon@google.com    for (int y = 0; y < h; ++y) {
125c69809745e6496564639e42ef998ad39adf7dfb8bsalomon@google.com        for (int x = 0; x < w; ++x) {
126c69809745e6496564639e42ef998ad39adf7dfb8bsalomon@google.com            SkPMColor* pixel = reinterpret_cast<SkPMColor*>(pixels + y * bitmap->rowBytes() + x * bitmap->bytesPerPixel());
12754f0d1b7113cb0dc184e522539aab1030a28a421sugoi@google.com            *pixel = getBitmapColor(x, y, w);
128c69809745e6496564639e42ef998ad39adf7dfb8bsalomon@google.com        }
129c69809745e6496564639e42ef998ad39adf7dfb8bsalomon@google.com    }
130c69809745e6496564639e42ef998ad39adf7dfb8bsalomon@google.com}
131c69809745e6496564639e42ef998ad39adf7dfb8bsalomon@google.com
132ddf94cf108ae430877f009bd67b9070341426947commit-bot@chromium.orgstatic bool checkPixel(SkPMColor a, SkPMColor b, bool didPremulConversion) {
133c43649962221c348d656d425a3fa9b29c78231d4bsalomon@google.com    if (!didPremulConversion) {
134c43649962221c348d656d425a3fa9b29c78231d4bsalomon@google.com        return a == b;
135c43649962221c348d656d425a3fa9b29c78231d4bsalomon@google.com    }
136c43649962221c348d656d425a3fa9b29c78231d4bsalomon@google.com    int32_t aA = static_cast<int32_t>(SkGetPackedA32(a));
137c43649962221c348d656d425a3fa9b29c78231d4bsalomon@google.com    int32_t aR = static_cast<int32_t>(SkGetPackedR32(a));
138c43649962221c348d656d425a3fa9b29c78231d4bsalomon@google.com    int32_t aG = static_cast<int32_t>(SkGetPackedG32(a));
139c43649962221c348d656d425a3fa9b29c78231d4bsalomon@google.com    int32_t aB = SkGetPackedB32(a);
140c43649962221c348d656d425a3fa9b29c78231d4bsalomon@google.com
141c43649962221c348d656d425a3fa9b29c78231d4bsalomon@google.com    int32_t bA = static_cast<int32_t>(SkGetPackedA32(b));
142c43649962221c348d656d425a3fa9b29c78231d4bsalomon@google.com    int32_t bR = static_cast<int32_t>(SkGetPackedR32(b));
143c43649962221c348d656d425a3fa9b29c78231d4bsalomon@google.com    int32_t bG = static_cast<int32_t>(SkGetPackedG32(b));
144c43649962221c348d656d425a3fa9b29c78231d4bsalomon@google.com    int32_t bB = static_cast<int32_t>(SkGetPackedB32(b));
145c43649962221c348d656d425a3fa9b29c78231d4bsalomon@google.com
146c43649962221c348d656d425a3fa9b29c78231d4bsalomon@google.com    return aA == bA &&
147c43649962221c348d656d425a3fa9b29c78231d4bsalomon@google.com           SkAbs32(aR - bR) <= 1 &&
148c43649962221c348d656d425a3fa9b29c78231d4bsalomon@google.com           SkAbs32(aG - bG) <= 1 &&
149c43649962221c348d656d425a3fa9b29c78231d4bsalomon@google.com           SkAbs32(aB - bB) <= 1;
150c43649962221c348d656d425a3fa9b29c78231d4bsalomon@google.com}
151c43649962221c348d656d425a3fa9b29c78231d4bsalomon@google.com
152c69809745e6496564639e42ef998ad39adf7dfb8bsalomon@google.com// checks the bitmap contains correct pixels after the readPixels
153c69809745e6496564639e42ef998ad39adf7dfb8bsalomon@google.com// if the bitmap was prefilled with pixels it checks that these weren't
154c69809745e6496564639e42ef998ad39adf7dfb8bsalomon@google.com// overwritten in the area outside the readPixels.
155ddf94cf108ae430877f009bd67b9070341426947commit-bot@chromium.orgstatic bool checkRead(skiatest::Reporter* reporter,
156ddf94cf108ae430877f009bd67b9070341426947commit-bot@chromium.org                      const SkBitmap& bitmap,
157ddf94cf108ae430877f009bd67b9070341426947commit-bot@chromium.org                      int x, int y,
158ddf94cf108ae430877f009bd67b9070341426947commit-bot@chromium.org                      bool checkCanvasPixels,
159a713f9c6f6a06d216d53e268b9c691941053dabfcommit-bot@chromium.org                      bool checkBitmapPixels) {
160a713f9c6f6a06d216d53e268b9c691941053dabfcommit-bot@chromium.org    SkASSERT(4 == bitmap.bytesPerPixel());
161c69809745e6496564639e42ef998ad39adf7dfb8bsalomon@google.com    SkASSERT(!bitmap.isNull());
1626850eab42ba4c2a7033a99824b02a2846ce0ef2absalomon@google.com    SkASSERT(checkCanvasPixels || checkBitmapPixels);
163d6176b0dcacb124539e0cfd051e6d93a9782f020rmistry@google.com
164a713f9c6f6a06d216d53e268b9c691941053dabfcommit-bot@chromium.org    const SkColorType ct = bitmap.colorType();
165a713f9c6f6a06d216d53e268b9c691941053dabfcommit-bot@chromium.org    const SkAlphaType at = bitmap.alphaType();
166a713f9c6f6a06d216d53e268b9c691941053dabfcommit-bot@chromium.org
167c69809745e6496564639e42ef998ad39adf7dfb8bsalomon@google.com    int bw = bitmap.width();
168c69809745e6496564639e42ef998ad39adf7dfb8bsalomon@google.com    int bh = bitmap.height();
169c69809745e6496564639e42ef998ad39adf7dfb8bsalomon@google.com
170c69809745e6496564639e42ef998ad39adf7dfb8bsalomon@google.com    SkIRect srcRect = SkIRect::MakeXYWH(x, y, bw, bh);
171c69809745e6496564639e42ef998ad39adf7dfb8bsalomon@google.com    SkIRect clippedSrcRect = DEV_RECT;
172c69809745e6496564639e42ef998ad39adf7dfb8bsalomon@google.com    if (!clippedSrcRect.intersect(srcRect)) {
173c69809745e6496564639e42ef998ad39adf7dfb8bsalomon@google.com        clippedSrcRect.setEmpty();
174c69809745e6496564639e42ef998ad39adf7dfb8bsalomon@google.com    }
175c69809745e6496564639e42ef998ad39adf7dfb8bsalomon@google.com    SkAutoLockPixels alp(bitmap);
176c69809745e6496564639e42ef998ad39adf7dfb8bsalomon@google.com    for (int by = 0; by < bh; ++by) {
177c69809745e6496564639e42ef998ad39adf7dfb8bsalomon@google.com        for (int bx = 0; bx < bw; ++bx) {
178c69809745e6496564639e42ef998ad39adf7dfb8bsalomon@google.com            int devx = bx + srcRect.fLeft;
179c69809745e6496564639e42ef998ad39adf7dfb8bsalomon@google.com            int devy = by + srcRect.fTop;
180d6176b0dcacb124539e0cfd051e6d93a9782f020rmistry@google.com
181a713f9c6f6a06d216d53e268b9c691941053dabfcommit-bot@chromium.org            const uint32_t* pixel = bitmap.getAddr32(bx, by);
182c69809745e6496564639e42ef998ad39adf7dfb8bsalomon@google.com
183c69809745e6496564639e42ef998ad39adf7dfb8bsalomon@google.com            if (clippedSrcRect.contains(devx, devy)) {
1846850eab42ba4c2a7033a99824b02a2846ce0ef2absalomon@google.com                if (checkCanvasPixels) {
1856850eab42ba4c2a7033a99824b02a2846ce0ef2absalomon@google.com                    SkPMColor canvasPixel = getCanvasColor(devx, devy);
186c43649962221c348d656d425a3fa9b29c78231d4bsalomon@google.com                    bool didPremul;
187a713f9c6f6a06d216d53e268b9c691941053dabfcommit-bot@chromium.org                    SkPMColor pmPixel = convertToPMColor(ct, at, pixel, &didPremul);
188c43649962221c348d656d425a3fa9b29c78231d4bsalomon@google.com                    bool check;
189c43649962221c348d656d425a3fa9b29c78231d4bsalomon@google.com                    REPORTER_ASSERT(reporter, check = checkPixel(pmPixel, canvasPixel, didPremul));
190c43649962221c348d656d425a3fa9b29c78231d4bsalomon@google.com                    if (!check) {
19172f3dca451f4739e20be0b4b198813677339205cbsalomon@google.com                        return false;
1926850eab42ba4c2a7033a99824b02a2846ce0ef2absalomon@google.com                    }
193c69809745e6496564639e42ef998ad39adf7dfb8bsalomon@google.com                }
1946850eab42ba4c2a7033a99824b02a2846ce0ef2absalomon@google.com            } else if (checkBitmapPixels) {
195a713f9c6f6a06d216d53e268b9c691941053dabfcommit-bot@chromium.org                REPORTER_ASSERT(reporter, getBitmapColor(bx, by, bw) == *pixel);
196a713f9c6f6a06d216d53e268b9c691941053dabfcommit-bot@chromium.org                if (getBitmapColor(bx, by, bw) != *pixel) {
19772f3dca451f4739e20be0b4b198813677339205cbsalomon@google.com                    return false;
198c69809745e6496564639e42ef998ad39adf7dfb8bsalomon@google.com                }
199c69809745e6496564639e42ef998ad39adf7dfb8bsalomon@google.com            }
200c69809745e6496564639e42ef998ad39adf7dfb8bsalomon@google.com        }
201c69809745e6496564639e42ef998ad39adf7dfb8bsalomon@google.com    }
20272f3dca451f4739e20be0b4b198813677339205cbsalomon@google.com    return true;
203c69809745e6496564639e42ef998ad39adf7dfb8bsalomon@google.com}
204c69809745e6496564639e42ef998ad39adf7dfb8bsalomon@google.com
205c69809745e6496564639e42ef998ad39adf7dfb8bsalomon@google.comenum BitmapInit {
206c69809745e6496564639e42ef998ad39adf7dfb8bsalomon@google.com    kFirstBitmapInit = 0,
207d6176b0dcacb124539e0cfd051e6d93a9782f020rmistry@google.com
208c69809745e6496564639e42ef998ad39adf7dfb8bsalomon@google.com    kNoPixels_BitmapInit = kFirstBitmapInit,
209c69809745e6496564639e42ef998ad39adf7dfb8bsalomon@google.com    kTight_BitmapInit,
210c69809745e6496564639e42ef998ad39adf7dfb8bsalomon@google.com    kRowBytes_BitmapInit,
211d6176b0dcacb124539e0cfd051e6d93a9782f020rmistry@google.com
212c69809745e6496564639e42ef998ad39adf7dfb8bsalomon@google.com    kBitmapInitCnt
213c69809745e6496564639e42ef998ad39adf7dfb8bsalomon@google.com};
214c69809745e6496564639e42ef998ad39adf7dfb8bsalomon@google.com
215ddf94cf108ae430877f009bd67b9070341426947commit-bot@chromium.orgstatic BitmapInit nextBMI(BitmapInit bmi) {
216c69809745e6496564639e42ef998ad39adf7dfb8bsalomon@google.com    int x = bmi;
217c69809745e6496564639e42ef998ad39adf7dfb8bsalomon@google.com    return static_cast<BitmapInit>(++x);
218c69809745e6496564639e42ef998ad39adf7dfb8bsalomon@google.com}
219c69809745e6496564639e42ef998ad39adf7dfb8bsalomon@google.com
220a713f9c6f6a06d216d53e268b9c691941053dabfcommit-bot@chromium.orgstatic void init_bitmap(SkBitmap* bitmap, const SkIRect& rect, BitmapInit init, SkColorType ct,
221a713f9c6f6a06d216d53e268b9c691941053dabfcommit-bot@chromium.org                        SkAlphaType at) {
222a713f9c6f6a06d216d53e268b9c691941053dabfcommit-bot@chromium.org    SkImageInfo info = SkImageInfo::Make(rect.width(), rect.height(), ct, at);
223fa9e5fa42a555712fb7a29d08d2ae2bdef0ed68ecommit-bot@chromium.org    size_t rowBytes = 0;
224c69809745e6496564639e42ef998ad39adf7dfb8bsalomon@google.com    bool alloc = true;
225c69809745e6496564639e42ef998ad39adf7dfb8bsalomon@google.com    switch (init) {
226c69809745e6496564639e42ef998ad39adf7dfb8bsalomon@google.com        case kNoPixels_BitmapInit:
227c69809745e6496564639e42ef998ad39adf7dfb8bsalomon@google.com            alloc = false;
228c69809745e6496564639e42ef998ad39adf7dfb8bsalomon@google.com        case kTight_BitmapInit:
229c69809745e6496564639e42ef998ad39adf7dfb8bsalomon@google.com            break;
230c69809745e6496564639e42ef998ad39adf7dfb8bsalomon@google.com        case kRowBytes_BitmapInit:
231fa9e5fa42a555712fb7a29d08d2ae2bdef0ed68ecommit-bot@chromium.org            rowBytes = (info.width() + 16) * sizeof(SkPMColor);
232c69809745e6496564639e42ef998ad39adf7dfb8bsalomon@google.com            break;
233c69809745e6496564639e42ef998ad39adf7dfb8bsalomon@google.com        default:
234c69809745e6496564639e42ef998ad39adf7dfb8bsalomon@google.com            SkASSERT(0);
235c69809745e6496564639e42ef998ad39adf7dfb8bsalomon@google.com            break;
236c69809745e6496564639e42ef998ad39adf7dfb8bsalomon@google.com    }
23702d6f546161e2c98d69066373cec3f54f3c46252skia.committer@gmail.com
238c69809745e6496564639e42ef998ad39adf7dfb8bsalomon@google.com    if (alloc) {
239fa9e5fa42a555712fb7a29d08d2ae2bdef0ed68ecommit-bot@chromium.org        bitmap->allocPixels(info);
240fa9e5fa42a555712fb7a29d08d2ae2bdef0ed68ecommit-bot@chromium.org    } else {
241a3264e53ee3f3c5d6a2c813df7e44b5b96d207f2commit-bot@chromium.org        bitmap->setInfo(info, rowBytes);
242c69809745e6496564639e42ef998ad39adf7dfb8bsalomon@google.com    }
243c69809745e6496564639e42ef998ad39adf7dfb8bsalomon@google.com}
244c69809745e6496564639e42ef998ad39adf7dfb8bsalomon@google.com
2454ee16bfaedb14aff8cf102f1f0722ff2529a9699tfarina@chromium.orgDEF_GPUTEST(ReadPixels, reporter, factory) {
246c69809745e6496564639e42ef998ad39adf7dfb8bsalomon@google.com    const SkIRect testRects[] = {
247c69809745e6496564639e42ef998ad39adf7dfb8bsalomon@google.com        // entire thing
248c69809745e6496564639e42ef998ad39adf7dfb8bsalomon@google.com        DEV_RECT,
249c69809745e6496564639e42ef998ad39adf7dfb8bsalomon@google.com        // larger on all sides
250c69809745e6496564639e42ef998ad39adf7dfb8bsalomon@google.com        SkIRect::MakeLTRB(-10, -10, DEV_W + 10, DEV_H + 10),
251c69809745e6496564639e42ef998ad39adf7dfb8bsalomon@google.com        // fully contained
252c69809745e6496564639e42ef998ad39adf7dfb8bsalomon@google.com        SkIRect::MakeLTRB(DEV_W / 4, DEV_H / 4, 3 * DEV_W / 4, 3 * DEV_H / 4),
253c69809745e6496564639e42ef998ad39adf7dfb8bsalomon@google.com        // outside top left
254c69809745e6496564639e42ef998ad39adf7dfb8bsalomon@google.com        SkIRect::MakeLTRB(-10, -10, -1, -1),
255c69809745e6496564639e42ef998ad39adf7dfb8bsalomon@google.com        // touching top left corner
256c69809745e6496564639e42ef998ad39adf7dfb8bsalomon@google.com        SkIRect::MakeLTRB(-10, -10, 0, 0),
257c69809745e6496564639e42ef998ad39adf7dfb8bsalomon@google.com        // overlapping top left corner
258c69809745e6496564639e42ef998ad39adf7dfb8bsalomon@google.com        SkIRect::MakeLTRB(-10, -10, DEV_W / 4, DEV_H / 4),
259c69809745e6496564639e42ef998ad39adf7dfb8bsalomon@google.com        // overlapping top left and top right corners
260c69809745e6496564639e42ef998ad39adf7dfb8bsalomon@google.com        SkIRect::MakeLTRB(-10, -10, DEV_W  + 10, DEV_H / 4),
261c69809745e6496564639e42ef998ad39adf7dfb8bsalomon@google.com        // touching entire top edge
262c69809745e6496564639e42ef998ad39adf7dfb8bsalomon@google.com        SkIRect::MakeLTRB(-10, -10, DEV_W  + 10, 0),
263c69809745e6496564639e42ef998ad39adf7dfb8bsalomon@google.com        // overlapping top right corner
264c69809745e6496564639e42ef998ad39adf7dfb8bsalomon@google.com        SkIRect::MakeLTRB(3 * DEV_W / 4, -10, DEV_W  + 10, DEV_H / 4),
265c69809745e6496564639e42ef998ad39adf7dfb8bsalomon@google.com        // contained in x, overlapping top edge
266c69809745e6496564639e42ef998ad39adf7dfb8bsalomon@google.com        SkIRect::MakeLTRB(DEV_W / 4, -10, 3 * DEV_W  / 4, DEV_H / 4),
267c69809745e6496564639e42ef998ad39adf7dfb8bsalomon@google.com        // outside top right corner
268c69809745e6496564639e42ef998ad39adf7dfb8bsalomon@google.com        SkIRect::MakeLTRB(DEV_W + 1, -10, DEV_W + 10, -1),
269c69809745e6496564639e42ef998ad39adf7dfb8bsalomon@google.com        // touching top right corner
270c69809745e6496564639e42ef998ad39adf7dfb8bsalomon@google.com        SkIRect::MakeLTRB(DEV_W, -10, DEV_W + 10, 0),
271c69809745e6496564639e42ef998ad39adf7dfb8bsalomon@google.com        // overlapping top left and bottom left corners
272c69809745e6496564639e42ef998ad39adf7dfb8bsalomon@google.com        SkIRect::MakeLTRB(-10, -10, DEV_W / 4, DEV_H + 10),
273c69809745e6496564639e42ef998ad39adf7dfb8bsalomon@google.com        // touching entire left edge
274c69809745e6496564639e42ef998ad39adf7dfb8bsalomon@google.com        SkIRect::MakeLTRB(-10, -10, 0, DEV_H + 10),
275c69809745e6496564639e42ef998ad39adf7dfb8bsalomon@google.com        // overlapping bottom left corner
276c69809745e6496564639e42ef998ad39adf7dfb8bsalomon@google.com        SkIRect::MakeLTRB(-10, 3 * DEV_H / 4, DEV_W / 4, DEV_H + 10),
277c69809745e6496564639e42ef998ad39adf7dfb8bsalomon@google.com        // contained in y, overlapping left edge
278c69809745e6496564639e42ef998ad39adf7dfb8bsalomon@google.com        SkIRect::MakeLTRB(-10, DEV_H / 4, DEV_W / 4, 3 * DEV_H / 4),
279c69809745e6496564639e42ef998ad39adf7dfb8bsalomon@google.com        // outside bottom left corner
280c69809745e6496564639e42ef998ad39adf7dfb8bsalomon@google.com        SkIRect::MakeLTRB(-10, DEV_H + 1, -1, DEV_H + 10),
281c69809745e6496564639e42ef998ad39adf7dfb8bsalomon@google.com        // touching bottom left corner
282c69809745e6496564639e42ef998ad39adf7dfb8bsalomon@google.com        SkIRect::MakeLTRB(-10, DEV_H, 0, DEV_H + 10),
283c69809745e6496564639e42ef998ad39adf7dfb8bsalomon@google.com        // overlapping bottom left and bottom right corners
284c69809745e6496564639e42ef998ad39adf7dfb8bsalomon@google.com        SkIRect::MakeLTRB(-10, 3 * DEV_H / 4, DEV_W + 10, DEV_H + 10),
285c69809745e6496564639e42ef998ad39adf7dfb8bsalomon@google.com        // touching entire left edge
286c69809745e6496564639e42ef998ad39adf7dfb8bsalomon@google.com        SkIRect::MakeLTRB(0, DEV_H, DEV_W, DEV_H + 10),
287c69809745e6496564639e42ef998ad39adf7dfb8bsalomon@google.com        // overlapping bottom right corner
288c69809745e6496564639e42ef998ad39adf7dfb8bsalomon@google.com        SkIRect::MakeLTRB(3 * DEV_W / 4, 3 * DEV_H / 4, DEV_W + 10, DEV_H + 10),
289c69809745e6496564639e42ef998ad39adf7dfb8bsalomon@google.com        // overlapping top right and bottom right corners
290c69809745e6496564639e42ef998ad39adf7dfb8bsalomon@google.com        SkIRect::MakeLTRB(3 * DEV_W / 4, -10, DEV_W + 10, DEV_H + 10),
291c69809745e6496564639e42ef998ad39adf7dfb8bsalomon@google.com    };
292c69809745e6496564639e42ef998ad39adf7dfb8bsalomon@google.com
2933cb406bb88f5aa09cf9f5a9554b4b1314cf1a2eesenorblanco@chromium.org    for (int dtype = 0; dtype < 3; ++dtype) {
29467b915de99e6b89d476907930ac8c27afb64d10ebsalomon@google.com        int glCtxTypeCnt = 1;
29567b915de99e6b89d476907930ac8c27afb64d10ebsalomon@google.com#if SK_SUPPORT_GPU
29667b915de99e6b89d476907930ac8c27afb64d10ebsalomon@google.com        if (0 != dtype)  {
29767b915de99e6b89d476907930ac8c27afb64d10ebsalomon@google.com            glCtxTypeCnt = GrContextFactory::kGLContextTypeCnt;
29867b915de99e6b89d476907930ac8c27afb64d10ebsalomon@google.com        }
29967b915de99e6b89d476907930ac8c27afb64d10ebsalomon@google.com#endif
30067b915de99e6b89d476907930ac8c27afb64d10ebsalomon@google.com        for (int glCtxType = 0; glCtxType < glCtxTypeCnt; ++glCtxType) {
3011f2f338e23789f3eef168dcbd8171a28820ba6c1robertphillips@google.com            SkAutoTUnref<SkBaseDevice> device;
30267b915de99e6b89d476907930ac8c27afb64d10ebsalomon@google.com            if (0 == dtype) {
30315a140599942f70e47380e3f700a825c7cece3b4commit-bot@chromium.org                SkImageInfo info = SkImageInfo::MakeN32Premul(DEV_W, DEV_H);
30415a140599942f70e47380e3f700a825c7cece3b4commit-bot@chromium.org                device.reset(SkBitmapDevice::Create(info));
30567b915de99e6b89d476907930ac8c27afb64d10ebsalomon@google.com            } else {
30667b915de99e6b89d476907930ac8c27afb64d10ebsalomon@google.com#if SK_SUPPORT_GPU
30767b915de99e6b89d476907930ac8c27afb64d10ebsalomon@google.com                GrContextFactory::GLContextType type =
30867b915de99e6b89d476907930ac8c27afb64d10ebsalomon@google.com                    static_cast<GrContextFactory::GLContextType>(glCtxType);
30967b915de99e6b89d476907930ac8c27afb64d10ebsalomon@google.com                if (!GrContextFactory::IsRenderingGLContext(type)) {
31067b915de99e6b89d476907930ac8c27afb64d10ebsalomon@google.com                    continue;
31167b915de99e6b89d476907930ac8c27afb64d10ebsalomon@google.com                }
31267b915de99e6b89d476907930ac8c27afb64d10ebsalomon@google.com                GrContext* context = factory->get(type);
31367b915de99e6b89d476907930ac8c27afb64d10ebsalomon@google.com                if (NULL == context) {
31467b915de99e6b89d476907930ac8c27afb64d10ebsalomon@google.com                    continue;
31567b915de99e6b89d476907930ac8c27afb64d10ebsalomon@google.com                }
3163cb406bb88f5aa09cf9f5a9554b4b1314cf1a2eesenorblanco@chromium.org                GrTextureDesc desc;
3173cb406bb88f5aa09cf9f5a9554b4b1314cf1a2eesenorblanco@chromium.org                desc.fFlags = kRenderTarget_GrTextureFlagBit | kNoStencil_GrTextureFlagBit;
3183cb406bb88f5aa09cf9f5a9554b4b1314cf1a2eesenorblanco@chromium.org                desc.fWidth = DEV_W;
3193cb406bb88f5aa09cf9f5a9554b4b1314cf1a2eesenorblanco@chromium.org                desc.fHeight = DEV_H;
320fec0bc3fc13481f5bcb341ab2d2d695911f39bd4bsalomon@google.com                desc.fConfig = kSkia8888_GrPixelConfig;
3213cb406bb88f5aa09cf9f5a9554b4b1314cf1a2eesenorblanco@chromium.org                desc.fOrigin = 1 == dtype ? kBottomLeft_GrSurfaceOrigin
3223cb406bb88f5aa09cf9f5a9554b4b1314cf1a2eesenorblanco@chromium.org                                          : kTopLeft_GrSurfaceOrigin;
3233cb406bb88f5aa09cf9f5a9554b4b1314cf1a2eesenorblanco@chromium.org                GrAutoScratchTexture ast(context, desc, GrContext::kExact_ScratchTexMatch);
3243cb406bb88f5aa09cf9f5a9554b4b1314cf1a2eesenorblanco@chromium.org                SkAutoTUnref<GrTexture> tex(ast.detach());
3253cb406bb88f5aa09cf9f5a9554b4b1314cf1a2eesenorblanco@chromium.org                device.reset(new SkGpuDevice(context, tex));
326cf8fb1f6f03fc77f9927564f9ef9abeeeec508d2bsalomon@google.com#else
32767b915de99e6b89d476907930ac8c27afb64d10ebsalomon@google.com                continue;
328cf8fb1f6f03fc77f9927564f9ef9abeeeec508d2bsalomon@google.com#endif
32967b915de99e6b89d476907930ac8c27afb64d10ebsalomon@google.com            }
33067b915de99e6b89d476907930ac8c27afb64d10ebsalomon@google.com            SkCanvas canvas(device);
33167b915de99e6b89d476907930ac8c27afb64d10ebsalomon@google.com            fillCanvas(&canvas);
332c69809745e6496564639e42ef998ad39adf7dfb8bsalomon@google.com
333a713f9c6f6a06d216d53e268b9c691941053dabfcommit-bot@chromium.org            static const struct {
334a713f9c6f6a06d216d53e268b9c691941053dabfcommit-bot@chromium.org                SkColorType fColorType;
335a713f9c6f6a06d216d53e268b9c691941053dabfcommit-bot@chromium.org                SkAlphaType fAlphaType;
336a713f9c6f6a06d216d53e268b9c691941053dabfcommit-bot@chromium.org            } gReadConfigs[] = {
337a713f9c6f6a06d216d53e268b9c691941053dabfcommit-bot@chromium.org                { kRGBA_8888_SkColorType,   kPremul_SkAlphaType },
338a713f9c6f6a06d216d53e268b9c691941053dabfcommit-bot@chromium.org                { kRGBA_8888_SkColorType,   kUnpremul_SkAlphaType },
339a713f9c6f6a06d216d53e268b9c691941053dabfcommit-bot@chromium.org                { kBGRA_8888_SkColorType,   kPremul_SkAlphaType },
340a713f9c6f6a06d216d53e268b9c691941053dabfcommit-bot@chromium.org                { kBGRA_8888_SkColorType,   kUnpremul_SkAlphaType },
34167b915de99e6b89d476907930ac8c27afb64d10ebsalomon@google.com            };
34267b915de99e6b89d476907930ac8c27afb64d10ebsalomon@google.com            for (size_t rect = 0; rect < SK_ARRAY_COUNT(testRects); ++rect) {
34367b915de99e6b89d476907930ac8c27afb64d10ebsalomon@google.com                const SkIRect& srcRect = testRects[rect];
344a713f9c6f6a06d216d53e268b9c691941053dabfcommit-bot@chromium.org                for (BitmapInit bmi = kFirstBitmapInit; bmi < kBitmapInitCnt; bmi = nextBMI(bmi)) {
34567b915de99e6b89d476907930ac8c27afb64d10ebsalomon@google.com                    for (size_t c = 0; c < SK_ARRAY_COUNT(gReadConfigs); ++c) {
34667b915de99e6b89d476907930ac8c27afb64d10ebsalomon@google.com                        SkBitmap bmp;
347a713f9c6f6a06d216d53e268b9c691941053dabfcommit-bot@chromium.org                        init_bitmap(&bmp, srcRect, bmi,
348a713f9c6f6a06d216d53e268b9c691941053dabfcommit-bot@chromium.org                                    gReadConfigs[c].fColorType, gReadConfigs[c].fAlphaType);
34967b915de99e6b89d476907930ac8c27afb64d10ebsalomon@google.com
35067b915de99e6b89d476907930ac8c27afb64d10ebsalomon@google.com                        // if the bitmap has pixels allocated before the readPixels,
35167b915de99e6b89d476907930ac8c27afb64d10ebsalomon@google.com                        // note that and fill them with pattern
35267b915de99e6b89d476907930ac8c27afb64d10ebsalomon@google.com                        bool startsWithPixels = !bmp.isNull();
35367b915de99e6b89d476907930ac8c27afb64d10ebsalomon@google.com                        if (startsWithPixels) {
35467b915de99e6b89d476907930ac8c27afb64d10ebsalomon@google.com                            fillBitmap(&bmp);
35567b915de99e6b89d476907930ac8c27afb64d10ebsalomon@google.com                        }
35667b915de99e6b89d476907930ac8c27afb64d10ebsalomon@google.com                        uint32_t idBefore = canvas.getDevice()->accessBitmap(false).getGenerationID();
357a713f9c6f6a06d216d53e268b9c691941053dabfcommit-bot@chromium.org                        bool success = canvas.readPixels(&bmp, srcRect.fLeft, srcRect.fTop);
35867b915de99e6b89d476907930ac8c27afb64d10ebsalomon@google.com                        uint32_t idAfter = canvas.getDevice()->accessBitmap(false).getGenerationID();
35967b915de99e6b89d476907930ac8c27afb64d10ebsalomon@google.com
36067b915de99e6b89d476907930ac8c27afb64d10ebsalomon@google.com                        // we expect to succeed when the read isn't fully clipped
36167b915de99e6b89d476907930ac8c27afb64d10ebsalomon@google.com                        // out.
36267b915de99e6b89d476907930ac8c27afb64d10ebsalomon@google.com                        bool expectSuccess = SkIRect::Intersects(srcRect, DEV_RECT);
36367b915de99e6b89d476907930ac8c27afb64d10ebsalomon@google.com                        // determine whether we expected the read to succeed.
36467b915de99e6b89d476907930ac8c27afb64d10ebsalomon@google.com                        REPORTER_ASSERT(reporter, success == expectSuccess);
36567b915de99e6b89d476907930ac8c27afb64d10ebsalomon@google.com                        // read pixels should never change the gen id
36667b915de99e6b89d476907930ac8c27afb64d10ebsalomon@google.com                        REPORTER_ASSERT(reporter, idBefore == idAfter);
36767b915de99e6b89d476907930ac8c27afb64d10ebsalomon@google.com
36867b915de99e6b89d476907930ac8c27afb64d10ebsalomon@google.com                        if (success || startsWithPixels) {
36967b915de99e6b89d476907930ac8c27afb64d10ebsalomon@google.com                            checkRead(reporter, bmp, srcRect.fLeft, srcRect.fTop,
370a713f9c6f6a06d216d53e268b9c691941053dabfcommit-bot@chromium.org                                      success, startsWithPixels);
37167b915de99e6b89d476907930ac8c27afb64d10ebsalomon@google.com                        } else {
37267b915de99e6b89d476907930ac8c27afb64d10ebsalomon@google.com                            // if we had no pixels beforehand and the readPixels
37367b915de99e6b89d476907930ac8c27afb64d10ebsalomon@google.com                            // failed then our bitmap should still not have pixels
37467b915de99e6b89d476907930ac8c27afb64d10ebsalomon@google.com                            REPORTER_ASSERT(reporter, bmp.isNull());
37567b915de99e6b89d476907930ac8c27afb64d10ebsalomon@google.com                        }
3766850eab42ba4c2a7033a99824b02a2846ce0ef2absalomon@google.com                    }
37767b915de99e6b89d476907930ac8c27afb64d10ebsalomon@google.com                    // check the old webkit version of readPixels that clips the
37867b915de99e6b89d476907930ac8c27afb64d10ebsalomon@google.com                    // bitmap size
37967b915de99e6b89d476907930ac8c27afb64d10ebsalomon@google.com                    SkBitmap wkbmp;
38067b915de99e6b89d476907930ac8c27afb64d10ebsalomon@google.com                    bool success = canvas.readPixels(srcRect, &wkbmp);
38167b915de99e6b89d476907930ac8c27afb64d10ebsalomon@google.com                    SkIRect clippedRect = DEV_RECT;
38267b915de99e6b89d476907930ac8c27afb64d10ebsalomon@google.com                    if (clippedRect.intersect(srcRect)) {
38367b915de99e6b89d476907930ac8c27afb64d10ebsalomon@google.com                        REPORTER_ASSERT(reporter, success);
38428fcae2ec77eb16a79e155f8d788b20457f1c951commit-bot@chromium.org                        REPORTER_ASSERT(reporter, kN32_SkColorType == wkbmp.colorType());
385a713f9c6f6a06d216d53e268b9c691941053dabfcommit-bot@chromium.org                        REPORTER_ASSERT(reporter, kPremul_SkAlphaType == wkbmp.alphaType());
38667b915de99e6b89d476907930ac8c27afb64d10ebsalomon@google.com                        checkRead(reporter, wkbmp, clippedRect.fLeft,
387a713f9c6f6a06d216d53e268b9c691941053dabfcommit-bot@chromium.org                                  clippedRect.fTop, true, false);
3886850eab42ba4c2a7033a99824b02a2846ce0ef2absalomon@google.com                    } else {
38967b915de99e6b89d476907930ac8c27afb64d10ebsalomon@google.com                        REPORTER_ASSERT(reporter, !success);
3906850eab42ba4c2a7033a99824b02a2846ce0ef2absalomon@google.com                    }
3916850eab42ba4c2a7033a99824b02a2846ce0ef2absalomon@google.com                }
392c69809745e6496564639e42ef998ad39adf7dfb8bsalomon@google.com            }
393c69809745e6496564639e42ef998ad39adf7dfb8bsalomon@google.com        }
394c69809745e6496564639e42ef998ad39adf7dfb8bsalomon@google.com    }
395c69809745e6496564639e42ef998ad39adf7dfb8bsalomon@google.com}
396