/*
* Copyright 2011 Google Inc.
*
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
#include "SkImageView.h"
#include "SkAnimator.h"
#include "SkBitmap.h"
#include "SkCanvas.h"
#include "SkImageDecoder.h"
#include "SkMatrix.h"
#include "SkSystemEventTypes.h"
#include "SkTime.h"
SkImageView::SkImageView()
{
fMatrix = nullptr;
fScaleType = kMatrix_ScaleType;
fData.fAnim = nullptr; // handles initializing the other union values
fDataIsAnim = true;
fUriIsValid = false; // an empty string is not valid
}
SkImageView::~SkImageView()
{
if (fMatrix)
sk_free(fMatrix);
this->freeData();
}
void SkImageView::getUri(SkString* uri) const
{
if (uri)
*uri = fUri;
}
void SkImageView::setUri(const char uri[])
{
if (!fUri.equals(uri))
{
fUri.set(uri);
this->onUriChange();
}
}
void SkImageView::setUri(const SkString& uri)
{
if (fUri != uri)
{
fUri = uri;
this->onUriChange();
}
}
void SkImageView::setScaleType(ScaleType st)
{
SkASSERT((unsigned)st <= kFitEnd_ScaleType);
if ((ScaleType)fScaleType != st)
{
fScaleType = SkToU8(st);
if (fUriIsValid)
this->inval(nullptr);
}
}
bool SkImageView::getImageMatrix(SkMatrix* matrix) const
{
if (fMatrix)
{
SkASSERT(!fMatrix->isIdentity());
if (matrix)
*matrix = *fMatrix;
return true;
}
else
{
if (matrix)
matrix->reset();
return false;
}
}
void SkImageView::setImageMatrix(const SkMatrix* matrix)
{
bool changed = false;
if (matrix && !matrix->isIdentity())
{
if (fMatrix == nullptr)
fMatrix = (SkMatrix*)sk_malloc_throw(sizeof(SkMatrix));
*fMatrix = *matrix;
changed = true;
}
else // set us to identity
{
if (fMatrix)
{
SkASSERT(!fMatrix->isIdentity());
sk_free(fMatrix);
fMatrix = nullptr;
changed = true;
}
}
// only redraw if we changed our matrix and we're not in scaleToFit mode
if (changed && this->getScaleType() == kMatrix_ScaleType && fUriIsValid)
this->inval(nullptr);
}
///////////////////////////////////////////////////////////////////////////////////////////////
bool SkImageView::onEvent(const SkEvent& evt)
{
if (evt.isType(SK_EventType_Inval))
{
if (fUriIsValid)
this->inval(nullptr);
return true;
}
return this->INHERITED::onEvent(evt);
}
static inline SkMatrix::ScaleToFit scaleTypeToScaleToFit(SkImageView::ScaleType st)
{
SkASSERT(st != SkImageView::kMatrix_ScaleType);
SkASSERT((unsigned)st <= SkImageView::kFitEnd_ScaleType);
SkASSERT(SkImageView::kFitXY_ScaleType - 1 == SkMatrix::kFill_ScaleToFit);
SkASSERT(SkImageView::kFitStart_ScaleType - 1 == SkMatrix::kStart_ScaleToFit);
SkASSERT(SkImageView::kFitCenter_ScaleType - 1 == SkMatrix::kCenter_ScaleToFit);
SkASSERT(SkImageView::kFitEnd_ScaleType - 1 == SkMatrix::kEnd_ScaleToFit);
return (SkMatrix::ScaleToFit)(st - 1);
}
void SkImageView::onDraw(SkCanvas* canvas)
{
SkRect src;
if (!this->getDataBounds(&src))
{
SkDEBUGCODE(canvas->drawColor(SK_ColorRED);)
return; // nothing to draw
}
SkAutoCanvasRestore restore(canvas, true);
SkMatrix matrix;
if (this->getScaleType() == kMatrix_ScaleType)
(void)this->getImageMatrix(&matrix);
else
{
SkRect dst;
dst.set(0, 0, this->width(), this->height());
matrix.setRectToRect(src, dst, scaleTypeToScaleToFit(this->getScaleType()));
}
canvas->concat(matrix);
SkPaint paint;
paint.setAntiAlias(true);
if (fDataIsAnim)
{
SkMSec now = SkTime::GetMSecs();
SkAnimator::DifferenceType diff = fData.fAnim->draw(canvas, &paint, now);
SkDEBUGF(("SkImageView : now = %X[%12.3f], diff = %d\n", now, now/1000., diff));
if (diff == SkAnimator::kDifferent)
this->inval(nullptr);
else if (diff == SkAnimator::kPartiallyDifferent)
{
SkRect bounds;
fData.fAnim->getInvalBounds(&bounds);
matrix.mapRect(&bounds); // get the bounds into view coordinates
this->inval(&bounds);
}
}
else
canvas->drawBitmap(*fData.fBitmap, 0, 0, &paint);
}
void SkImageView::onInflate(const SkDOM& dom, const SkDOMNode* node)
{
this->INHERITED::onInflate(dom, node);
const char* src = dom.findAttr(node, "src");
if (src)
this->setUri(src);
int index = dom.findList(node, "scaleType", "matrix,fitXY,fitStart,fitCenter,fitEnd");
if (index >= 0)
this->setScaleType((ScaleType)index);
// need inflate syntax/reader for matrix
}
/////////////////////////////////////////////////////////////////////////////////////
void SkImageView::onUriChange()
{
if (this->freeData())
this->inval(nullptr);
fUriIsValid = true; // give ensureUriIsLoaded() a shot at the new uri
}
bool SkImageView::freeData()
{
if (fData.fAnim) // test is valid for all union values
{
if (fDataIsAnim)
delete fData.fAnim;
else
delete fData.fBitmap;
fData.fAnim = nullptr; // valid for all union values
return true;
}
return false;
}
bool SkImageView::getDataBounds(SkRect* bounds)
{
SkASSERT(bounds);
if (this->ensureUriIsLoaded())
{
SkScalar width, height;
if (fDataIsAnim)
{
if (SkScalarIsNaN(width = fData.fAnim->getScalar("dimensions", "x")) ||
SkScalarIsNaN(height = fData.fAnim->getScalar("dimensions", "y")))
{
// cons up fake bounds
width = this->width();
height = this->height();
}
}
else
{
width = SkIntToScalar(fData.fBitmap->width());
height = SkIntToScalar(fData.fBitmap->height());
}
bounds->set(0, 0, width, height);
return true;
}
return false;
}
bool SkImageView::ensureUriIsLoaded()
{
if (fData.fAnim) // test is valid for all union values
{
SkASSERT(fUriIsValid);
return true;
}
if (!fUriIsValid)
return false;
// try to load the url
if (fUri.endsWith(".xml")) // assume it is screenplay
{
SkAnimator* anim = new SkAnimator;
if (!anim->decodeURI(fUri.c_str()))
{
delete anim;
fUriIsValid = false;
return false;
}
anim->setHostEventSink(this);
fData.fAnim = anim;
fDataIsAnim = true;
}
else // assume it is an image format
{
#if 0
SkBitmap* bitmap = new SkBitmap;
if (!SkImageDecoder::DecodeURL(fUri.c_str(), bitmap))
{
delete bitmap;
fUriIsValid = false;
return false;
}
fData.fBitmap = bitmap;
fDataIsAnim = false;
#else
return false;
#endif
}
return true;
}