1
2/*
3 * Copyright 2011 Google Inc.
4 *
5 * Use of this source code is governed by a BSD-style license that can be
6 * found in the LICENSE file.
7 */
8#include "SkImageView.h"
9#include "SkAnimator.h"
10#include "SkBitmap.h"
11#include "SkCanvas.h"
12#include "SkImageDecoder.h"
13#include "SkMatrix.h"
14#include "SkSystemEventTypes.h"
15#include "SkTime.h"
16
17SkImageView::SkImageView()
18{
19    fMatrix        = NULL;
20    fScaleType    = kMatrix_ScaleType;
21
22    fData.fAnim    = NULL;        // handles initializing the other union values
23    fDataIsAnim    = true;
24
25    fUriIsValid    = false;    // an empty string is not valid
26}
27
28SkImageView::~SkImageView()
29{
30    if (fMatrix)
31        sk_free(fMatrix);
32
33    this->freeData();
34}
35
36void SkImageView::getUri(SkString* uri) const
37{
38    if (uri)
39        *uri = fUri;
40}
41
42void SkImageView::setUri(const char uri[])
43{
44    if (!fUri.equals(uri))
45    {
46        fUri.set(uri);
47        this->onUriChange();
48    }
49}
50
51void SkImageView::setUri(const SkString& uri)
52{
53    if (fUri != uri)
54    {
55        fUri = uri;
56        this->onUriChange();
57    }
58}
59
60void SkImageView::setScaleType(ScaleType st)
61{
62    SkASSERT((unsigned)st <= kFitEnd_ScaleType);
63
64    if ((ScaleType)fScaleType != st)
65    {
66        fScaleType = SkToU8(st);
67        if (fUriIsValid)
68            this->inval(NULL);
69    }
70}
71
72bool SkImageView::getImageMatrix(SkMatrix* matrix) const
73{
74    if (fMatrix)
75    {
76        SkASSERT(!fMatrix->isIdentity());
77        if (matrix)
78            *matrix = *fMatrix;
79        return true;
80    }
81    else
82    {
83        if (matrix)
84            matrix->reset();
85        return false;
86    }
87}
88
89void SkImageView::setImageMatrix(const SkMatrix* matrix)
90{
91    bool changed = false;
92
93    if (matrix && !matrix->isIdentity())
94    {
95        if (fMatrix == NULL)
96            fMatrix = (SkMatrix*)sk_malloc_throw(sizeof(SkMatrix));
97        *fMatrix = *matrix;
98        changed = true;
99    }
100    else    // set us to identity
101    {
102        if (fMatrix)
103        {
104            SkASSERT(!fMatrix->isIdentity());
105            sk_free(fMatrix);
106            fMatrix = NULL;
107            changed = true;
108        }
109    }
110
111    // only redraw if we changed our matrix and we're not in scaleToFit mode
112    if (changed && this->getScaleType() == kMatrix_ScaleType && fUriIsValid)
113        this->inval(NULL);
114}
115
116///////////////////////////////////////////////////////////////////////////////////////////////
117
118bool SkImageView::onEvent(const SkEvent& evt)
119{
120    if (evt.isType(SK_EventType_Inval))
121    {
122        if (fUriIsValid)
123            this->inval(NULL);
124        return true;
125    }
126    return this->INHERITED::onEvent(evt);
127}
128
129static inline SkMatrix::ScaleToFit scaleTypeToScaleToFit(SkImageView::ScaleType st)
130{
131    SkASSERT(st != SkImageView::kMatrix_ScaleType);
132    SkASSERT((unsigned)st <= SkImageView::kFitEnd_ScaleType);
133
134    SkASSERT(SkImageView::kFitXY_ScaleType - 1 == SkMatrix::kFill_ScaleToFit);
135    SkASSERT(SkImageView::kFitStart_ScaleType - 1 == SkMatrix::kStart_ScaleToFit);
136    SkASSERT(SkImageView::kFitCenter_ScaleType - 1 == SkMatrix::kCenter_ScaleToFit);
137    SkASSERT(SkImageView::kFitEnd_ScaleType - 1 == SkMatrix::kEnd_ScaleToFit);
138
139    return (SkMatrix::ScaleToFit)(st - 1);
140}
141
142void SkImageView::onDraw(SkCanvas* canvas)
143{
144    SkRect    src;
145    if (!this->getDataBounds(&src))
146    {
147        SkDEBUGCODE(canvas->drawColor(SK_ColorRED);)
148        return;        // nothing to draw
149    }
150
151    SkAutoCanvasRestore    restore(canvas, true);
152    SkMatrix            matrix;
153
154    if (this->getScaleType() == kMatrix_ScaleType)
155        (void)this->getImageMatrix(&matrix);
156    else
157    {
158        SkRect    dst;
159        dst.set(0, 0, this->width(), this->height());
160        matrix.setRectToRect(src, dst, scaleTypeToScaleToFit(this->getScaleType()));
161    }
162    canvas->concat(matrix);
163
164    SkPaint    paint;
165
166    paint.setAntiAlias(true);
167
168    if (fDataIsAnim)
169    {
170        SkMSec    now = SkTime::GetMSecs();
171
172        SkAnimator::DifferenceType diff = fData.fAnim->draw(canvas, &paint, now);
173
174SkDEBUGF(("SkImageView : now = %X[%12.3f], diff = %d\n", now, now/1000., diff));
175
176        if (diff == SkAnimator::kDifferent)
177            this->inval(NULL);
178        else if (diff == SkAnimator::kPartiallyDifferent)
179        {
180            SkRect    bounds;
181            fData.fAnim->getInvalBounds(&bounds);
182            matrix.mapRect(&bounds);    // get the bounds into view coordinates
183            this->inval(&bounds);
184        }
185    }
186    else
187        canvas->drawBitmap(*fData.fBitmap, 0, 0, &paint);
188}
189
190void SkImageView::onInflate(const SkDOM& dom, const SkDOMNode* node)
191{
192    this->INHERITED::onInflate(dom, node);
193
194    const char* src = dom.findAttr(node, "src");
195    if (src)
196        this->setUri(src);
197
198    int    index = dom.findList(node, "scaleType", "matrix,fitXY,fitStart,fitCenter,fitEnd");
199    if (index >= 0)
200        this->setScaleType((ScaleType)index);
201
202    // need inflate syntax/reader for matrix
203}
204
205/////////////////////////////////////////////////////////////////////////////////////
206
207void SkImageView::onUriChange()
208{
209    if (this->freeData())
210        this->inval(NULL);
211    fUriIsValid = true;        // give ensureUriIsLoaded() a shot at the new uri
212}
213
214bool SkImageView::freeData()
215{
216    if (fData.fAnim)    // test is valid for all union values
217    {
218        if (fDataIsAnim)
219            delete fData.fAnim;
220        else
221            delete fData.fBitmap;
222
223        fData.fAnim = NULL;    // valid for all union values
224        return true;
225    }
226    return false;
227}
228
229bool SkImageView::getDataBounds(SkRect* bounds)
230{
231    SkASSERT(bounds);
232
233    if (this->ensureUriIsLoaded())
234    {
235        SkScalar width, height;
236
237        if (fDataIsAnim)
238        {
239            if (SkScalarIsNaN(width = fData.fAnim->getScalar("dimensions", "x")) ||
240                SkScalarIsNaN(height = fData.fAnim->getScalar("dimensions", "y")))
241            {
242                // cons up fake bounds
243                width = this->width();
244                height = this->height();
245            }
246        }
247        else
248        {
249            width = SkIntToScalar(fData.fBitmap->width());
250            height = SkIntToScalar(fData.fBitmap->height());
251        }
252        bounds->set(0, 0, width, height);
253        return true;
254    }
255    return false;
256}
257
258bool SkImageView::ensureUriIsLoaded()
259{
260    if (fData.fAnim)    // test is valid for all union values
261    {
262        SkASSERT(fUriIsValid);
263        return true;
264    }
265    if (!fUriIsValid)
266        return false;
267
268    // try to load the url
269    if (fUri.endsWith(".xml"))    // assume it is screenplay
270    {
271        SkAnimator* anim = new SkAnimator;
272
273        if (!anim->decodeURI(fUri.c_str()))
274        {
275            delete anim;
276            fUriIsValid = false;
277            return false;
278        }
279        anim->setHostEventSink(this);
280
281        fData.fAnim = anim;
282        fDataIsAnim = true;
283    }
284    else    // assume it is an image format
285    {
286    #if 0
287        SkBitmap* bitmap = new SkBitmap;
288
289        if (!SkImageDecoder::DecodeURL(fUri.c_str(), bitmap))
290        {
291            delete bitmap;
292            fUriIsValid = false;
293            return false;
294        }
295        fData.fBitmap = bitmap;
296        fDataIsAnim = false;
297    #else
298        return false;
299    #endif
300    }
301    return true;
302}
303