1/* 2 * Copyright 2008 The Android Open Source Project 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 "SkAtomics.h" 9#include "SkBitmap.h" 10#include "SkColorData.h" 11#include "SkColorTable.h" 12#include "SkConvertPixels.h" 13#include "SkData.h" 14#include "SkFilterQuality.h" 15#include "SkHalf.h" 16#include "SkImageInfoPriv.h" 17#include "SkMallocPixelRef.h" 18#include "SkMask.h" 19#include "SkMaskFilterBase.h" 20#include "SkMath.h" 21#include "SkPixelRef.h" 22#include "SkPixmapPriv.h" 23#include "SkReadBuffer.h" 24#include "SkRect.h" 25#include "SkScalar.h" 26#include "SkTemplates.h" 27#include "SkUnPreMultiply.h" 28#include "SkWriteBuffer.h" 29#include "SkWritePixelsRec.h" 30 31#include <string.h> 32 33static bool reset_return_false(SkBitmap* bm) { 34 bm->reset(); 35 return false; 36} 37 38SkBitmap::SkBitmap() : fFlags(0) {} 39 40SkBitmap::SkBitmap(const SkBitmap& src) 41 : fPixelRef (src.fPixelRef) 42 , fPixmap (src.fPixmap) 43 , fFlags (src.fFlags) 44{ 45 SkDEBUGCODE(src.validate();) 46 SkDEBUGCODE(this->validate();) 47} 48 49SkBitmap::SkBitmap(SkBitmap&& other) 50 : fPixelRef (std::move(other.fPixelRef)) 51 , fPixmap (std::move(other.fPixmap)) 52 , fFlags (other.fFlags) 53{ 54 SkASSERT(!other.fPixelRef); 55 other.fPixmap.reset(); 56 other.fFlags = 0; 57} 58 59SkBitmap::~SkBitmap() {} 60 61SkBitmap& SkBitmap::operator=(const SkBitmap& src) { 62 if (this != &src) { 63 fPixelRef = src.fPixelRef; 64 fPixmap = src.fPixmap; 65 fFlags = src.fFlags; 66 } 67 SkDEBUGCODE(this->validate();) 68 return *this; 69} 70 71SkBitmap& SkBitmap::operator=(SkBitmap&& other) { 72 if (this != &other) { 73 fPixelRef = std::move(other.fPixelRef); 74 fPixmap = std::move(other.fPixmap); 75 fFlags = other.fFlags; 76 SkASSERT(!other.fPixelRef); 77 other.fPixmap.reset(); 78 other.fFlags = 0; 79 } 80 return *this; 81} 82 83void SkBitmap::swap(SkBitmap& other) { 84 SkTSwap(*this, other); 85 SkDEBUGCODE(this->validate();) 86} 87 88void SkBitmap::reset() { 89 fPixelRef = nullptr; // Free pixels. 90 fPixmap.reset(); 91 fFlags = 0; 92} 93 94void SkBitmap::getBounds(SkRect* bounds) const { 95 SkASSERT(bounds); 96 *bounds = SkRect::Make(this->dimensions()); 97} 98 99void SkBitmap::getBounds(SkIRect* bounds) const { 100 SkASSERT(bounds); 101 *bounds = fPixmap.bounds(); 102} 103 104/////////////////////////////////////////////////////////////////////////////// 105 106bool SkBitmap::setInfo(const SkImageInfo& info, size_t rowBytes) { 107 SkAlphaType newAT = info.alphaType(); 108 if (!SkColorTypeValidateAlphaType(info.colorType(), info.alphaType(), &newAT)) { 109 return reset_return_false(this); 110 } 111 // don't look at info.alphaType(), since newAT is the real value... 112 113 // require that rowBytes fit in 31bits 114 int64_t mrb = info.minRowBytes64(); 115 if ((int32_t)mrb != mrb) { 116 return reset_return_false(this); 117 } 118 if ((int64_t)rowBytes != (int32_t)rowBytes) { 119 return reset_return_false(this); 120 } 121 122 if (info.width() < 0 || info.height() < 0) { 123 return reset_return_false(this); 124 } 125 126 if (kUnknown_SkColorType == info.colorType()) { 127 rowBytes = 0; 128 } else if (0 == rowBytes) { 129 rowBytes = (size_t)mrb; 130 } else if (!info.validRowBytes(rowBytes)) { 131 return reset_return_false(this); 132 } 133 134 fPixelRef = nullptr; // Free pixels. 135 fPixmap.reset(info.makeAlphaType(newAT), nullptr, SkToU32(rowBytes)); 136 SkDEBUGCODE(this->validate();) 137 return true; 138} 139 140 141 142bool SkBitmap::setAlphaType(SkAlphaType newAlphaType) { 143 if (!SkColorTypeValidateAlphaType(this->colorType(), newAlphaType, &newAlphaType)) { 144 return false; 145 } 146 if (this->alphaType() != newAlphaType) { 147 auto newInfo = fPixmap.info().makeAlphaType(newAlphaType); 148 fPixmap.reset(std::move(newInfo), fPixmap.addr(), fPixmap.rowBytes()); 149 } 150 SkDEBUGCODE(this->validate();) 151 return true; 152} 153 154SkIPoint SkBitmap::pixelRefOrigin() const { 155 const char* addr = (const char*)fPixmap.addr(); 156 const char* pix = (const char*)(fPixelRef ? fPixelRef->pixels() : nullptr); 157 size_t rb = this->rowBytes(); 158 if (!pix || 0 == rb) { 159 return {0, 0}; 160 } 161 SkASSERT(this->bytesPerPixel() > 0); 162 SkASSERT(this->bytesPerPixel() == (1 << this->shiftPerPixel())); 163 SkASSERT(addr >= pix); 164 size_t off = addr - pix; 165 return {SkToS32((off % rb) >> this->shiftPerPixel()), SkToS32(off / rb)}; 166} 167 168void SkBitmap::setPixelRef(sk_sp<SkPixelRef> pr, int dx, int dy) { 169#ifdef SK_DEBUG 170 if (pr) { 171 if (kUnknown_SkColorType != this->colorType()) { 172 SkASSERT(dx >= 0 && this->width() + dx <= pr->width()); 173 SkASSERT(dy >= 0 && this->height() + dy <= pr->height()); 174 } 175 } 176#endif 177 fPixelRef = kUnknown_SkColorType != this->colorType() ? std::move(pr) : nullptr; 178 void* p = nullptr; 179 size_t rowBytes = this->rowBytes(); 180 // ignore dx,dy if there is no pixelref 181 if (fPixelRef) { 182 rowBytes = fPixelRef->rowBytes(); 183 // TODO(reed): Enforce that PixelRefs must have non-null pixels. 184 p = fPixelRef->pixels(); 185 if (p) { 186 p = (char*)p + dy * rowBytes + dx * this->bytesPerPixel(); 187 } 188 } 189 SkPixmapPriv::ResetPixmapKeepInfo(&fPixmap, p, rowBytes); 190 SkDEBUGCODE(this->validate();) 191} 192 193void SkBitmap::setPixels(void* p) { 194 if (nullptr == p) { 195 this->setPixelRef(nullptr, 0, 0); 196 return; 197 } 198 199 if (kUnknown_SkColorType == this->colorType()) { 200 this->setPixelRef(nullptr, 0, 0); 201 return; 202 } 203 204 this->setPixelRef(SkMallocPixelRef::MakeDirect(this->info(), p, this->rowBytes()), 0, 0); 205 if (!fPixelRef) { 206 return; 207 } 208 SkDEBUGCODE(this->validate();) 209} 210 211bool SkBitmap::tryAllocPixels(Allocator* allocator) { 212 HeapAllocator stdalloc; 213 214 if (nullptr == allocator) { 215 allocator = &stdalloc; 216 } 217 return allocator->allocPixelRef(this); 218} 219 220/////////////////////////////////////////////////////////////////////////////// 221 222bool SkBitmap::tryAllocPixels(const SkImageInfo& requestedInfo, size_t rowBytes) { 223 if (!this->setInfo(requestedInfo, rowBytes)) { 224 return reset_return_false(this); 225 } 226 227 // setInfo may have corrected info (e.g. 565 is always opaque). 228 const SkImageInfo& correctedInfo = this->info(); 229 if (kUnknown_SkColorType == correctedInfo.colorType()) { 230 return true; 231 } 232 // setInfo may have computed a valid rowbytes if 0 were passed in 233 rowBytes = this->rowBytes(); 234 235 sk_sp<SkPixelRef> pr = SkMallocPixelRef::MakeAllocate(correctedInfo, rowBytes); 236 if (!pr) { 237 return reset_return_false(this); 238 } 239 this->setPixelRef(std::move(pr), 0, 0); 240 if (nullptr == this->getPixels()) { 241 return reset_return_false(this); 242 } 243 SkDEBUGCODE(this->validate();) 244 return true; 245} 246 247bool SkBitmap::tryAllocPixelsFlags(const SkImageInfo& requestedInfo, uint32_t allocFlags) { 248 if (!this->setInfo(requestedInfo)) { 249 return reset_return_false(this); 250 } 251 252 // setInfo may have corrected info (e.g. 565 is always opaque). 253 const SkImageInfo& correctedInfo = this->info(); 254 255 sk_sp<SkPixelRef> pr = (allocFlags & kZeroPixels_AllocFlag) ? 256 SkMallocPixelRef::MakeZeroed(correctedInfo, correctedInfo.minRowBytes()) : 257 SkMallocPixelRef::MakeAllocate(correctedInfo, correctedInfo.minRowBytes()); 258 if (!pr) { 259 return reset_return_false(this); 260 } 261 this->setPixelRef(std::move(pr), 0, 0); 262 if (nullptr == this->getPixels()) { 263 return reset_return_false(this); 264 } 265 SkDEBUGCODE(this->validate();) 266 return true; 267} 268 269static void invoke_release_proc(void (*proc)(void* pixels, void* ctx), void* pixels, void* ctx) { 270 if (proc) { 271 proc(pixels, ctx); 272 } 273} 274 275bool SkBitmap::installPixels(const SkImageInfo& requestedInfo, void* pixels, size_t rb, 276 void (*releaseProc)(void* addr, void* context), void* context) { 277 if (!this->setInfo(requestedInfo, rb)) { 278 invoke_release_proc(releaseProc, pixels, context); 279 this->reset(); 280 return false; 281 } 282 if (nullptr == pixels) { 283 invoke_release_proc(releaseProc, pixels, context); 284 return true; // we behaved as if they called setInfo() 285 } 286 287 // setInfo may have corrected info (e.g. 565 is always opaque). 288 const SkImageInfo& correctedInfo = this->info(); 289 290 sk_sp<SkPixelRef> pr = SkMallocPixelRef::MakeWithProc(correctedInfo, rb, pixels, 291 releaseProc, context); 292 if (!pr) { 293 this->reset(); 294 return false; 295 } 296 297 this->setPixelRef(std::move(pr), 0, 0); 298 SkDEBUGCODE(this->validate();) 299 return true; 300} 301 302bool SkBitmap::installPixels(const SkPixmap& pixmap) { 303 return this->installPixels(pixmap.info(), pixmap.writable_addr(), pixmap.rowBytes(), 304 nullptr, nullptr); 305} 306 307bool SkBitmap::installMaskPixels(const SkMask& mask) { 308 if (SkMask::kA8_Format != mask.fFormat) { 309 this->reset(); 310 return false; 311 } 312 return this->installPixels(SkImageInfo::MakeA8(mask.fBounds.width(), 313 mask.fBounds.height()), 314 mask.fImage, mask.fRowBytes); 315} 316 317/////////////////////////////////////////////////////////////////////////////// 318 319uint32_t SkBitmap::getGenerationID() const { 320 return fPixelRef ? fPixelRef->getGenerationID() : 0; 321} 322 323void SkBitmap::notifyPixelsChanged() const { 324 SkASSERT(!this->isImmutable()); 325 if (fPixelRef) { 326 fPixelRef->notifyPixelsChanged(); 327 } 328} 329 330/////////////////////////////////////////////////////////////////////////////// 331 332/** We explicitly use the same allocator for our pixels that SkMask does, 333 so that we can freely assign memory allocated by one class to the other. 334 */ 335bool SkBitmap::HeapAllocator::allocPixelRef(SkBitmap* dst) { 336 const SkImageInfo info = dst->info(); 337 if (kUnknown_SkColorType == info.colorType()) { 338// SkDebugf("unsupported config for info %d\n", dst->config()); 339 return false; 340 } 341 342 sk_sp<SkPixelRef> pr = SkMallocPixelRef::MakeAllocate(info, dst->rowBytes()); 343 if (!pr) { 344 return false; 345 } 346 347 dst->setPixelRef(std::move(pr), 0, 0); 348 SkDEBUGCODE(dst->validate();) 349 return true; 350} 351 352/////////////////////////////////////////////////////////////////////////////// 353 354bool SkBitmap::isImmutable() const { 355 return fPixelRef ? fPixelRef->isImmutable() : false; 356} 357 358void SkBitmap::setImmutable() { 359 if (fPixelRef) { 360 fPixelRef->setImmutable(); 361 } 362} 363 364bool SkBitmap::isVolatile() const { 365 return (fFlags & kImageIsVolatile_Flag) != 0; 366} 367 368void SkBitmap::setIsVolatile(bool isVolatile) { 369 if (isVolatile) { 370 fFlags |= kImageIsVolatile_Flag; 371 } else { 372 fFlags &= ~kImageIsVolatile_Flag; 373 } 374} 375 376void* SkBitmap::getAddr(int x, int y) const { 377 SkASSERT((unsigned)x < (unsigned)this->width()); 378 SkASSERT((unsigned)y < (unsigned)this->height()); 379 380 char* base = (char*)this->getPixels(); 381 if (base) { 382 base += y * this->rowBytes(); 383 switch (this->colorType()) { 384 case kRGBA_F16_SkColorType: 385 base += x << 3; 386 break; 387 case kRGBA_8888_SkColorType: 388 case kBGRA_8888_SkColorType: 389 base += x << 2; 390 break; 391 case kARGB_4444_SkColorType: 392 case kRGB_565_SkColorType: 393 base += x << 1; 394 break; 395 case kAlpha_8_SkColorType: 396 case kGray_8_SkColorType: 397 base += x; 398 break; 399 default: 400 SkDEBUGFAIL("Can't return addr for config"); 401 base = nullptr; 402 break; 403 } 404 } 405 return base; 406} 407 408/////////////////////////////////////////////////////////////////////////////// 409/////////////////////////////////////////////////////////////////////////////// 410 411void SkBitmap::erase(SkColor c, const SkIRect& area) const { 412 SkDEBUGCODE(this->validate();) 413 414 switch (this->colorType()) { 415 case kUnknown_SkColorType: 416 // TODO: can we ASSERT that we never get here? 417 return; // can't erase. Should we bzero so the memory is not uninitialized? 418 default: 419 break; 420 } 421 422 SkPixmap result; 423 if (!this->peekPixels(&result)) { 424 return; 425 } 426 427 if (result.erase(c, area)) { 428 this->notifyPixelsChanged(); 429 } 430} 431 432void SkBitmap::eraseColor(SkColor c) const { 433 this->erase(c, SkIRect::MakeWH(this->width(), this->height())); 434} 435 436////////////////////////////////////////////////////////////////////////////////////// 437////////////////////////////////////////////////////////////////////////////////////// 438 439bool SkBitmap::extractSubset(SkBitmap* result, const SkIRect& subset) const { 440 SkDEBUGCODE(this->validate();) 441 442 if (nullptr == result || !fPixelRef) { 443 return false; // no src pixels 444 } 445 446 SkIRect srcRect, r; 447 srcRect.set(0, 0, this->width(), this->height()); 448 if (!r.intersect(srcRect, subset)) { 449 return false; // r is empty (i.e. no intersection) 450 } 451 452 // If the upper left of the rectangle was outside the bounds of this SkBitmap, we should have 453 // exited above. 454 SkASSERT(static_cast<unsigned>(r.fLeft) < static_cast<unsigned>(this->width())); 455 SkASSERT(static_cast<unsigned>(r.fTop) < static_cast<unsigned>(this->height())); 456 457 SkBitmap dst; 458 dst.setInfo(this->info().makeWH(r.width(), r.height()), this->rowBytes()); 459 dst.setIsVolatile(this->isVolatile()); 460 461 if (fPixelRef) { 462 SkIPoint origin = this->pixelRefOrigin(); 463 // share the pixelref with a custom offset 464 dst.setPixelRef(fPixelRef, origin.x() + r.fLeft, origin.y() + r.fTop); 465 } 466 SkDEBUGCODE(dst.validate();) 467 468 // we know we're good, so commit to result 469 result->swap(dst); 470 return true; 471} 472 473/////////////////////////////////////////////////////////////////////////////// 474 475bool SkBitmap::readPixels(const SkImageInfo& requestedDstInfo, void* dstPixels, size_t dstRB, 476 int x, int y, SkTransferFunctionBehavior behavior) const { 477 SkPixmap src; 478 if (!this->peekPixels(&src)) { 479 return false; 480 } 481 return src.readPixels(requestedDstInfo, dstPixels, dstRB, x, y, behavior); 482} 483 484bool SkBitmap::readPixels(const SkPixmap& dst, int srcX, int srcY) const { 485 return this->readPixels(dst.info(), dst.writable_addr(), dst.rowBytes(), srcX, srcY); 486} 487 488bool SkBitmap::writePixels(const SkPixmap& src, int dstX, int dstY, 489 SkTransferFunctionBehavior behavior) { 490 if (!SkImageInfoValidConversion(this->info(), src.info())) { 491 return false; 492 } 493 494 SkWritePixelsRec rec(src.info(), src.addr(), src.rowBytes(), dstX, dstY); 495 if (!rec.trim(this->width(), this->height())) { 496 return false; 497 } 498 499 void* dstPixels = this->getAddr(rec.fX, rec.fY); 500 const SkImageInfo dstInfo = this->info().makeWH(rec.fInfo.width(), rec.fInfo.height()); 501 SkConvertPixels(dstInfo, dstPixels, this->rowBytes(), rec.fInfo, rec.fPixels, rec.fRowBytes, 502 nullptr, behavior); 503 this->notifyPixelsChanged(); 504 return true; 505} 506 507/////////////////////////////////////////////////////////////////////////////// 508 509static bool GetBitmapAlpha(const SkBitmap& src, uint8_t* SK_RESTRICT alpha, int alphaRowBytes) { 510 SkASSERT(alpha != nullptr); 511 SkASSERT(alphaRowBytes >= src.width()); 512 513 SkPixmap pmap; 514 if (!src.peekPixels(&pmap)) { 515 for (int y = 0; y < src.height(); ++y) { 516 memset(alpha, 0, src.width()); 517 alpha += alphaRowBytes; 518 } 519 return false; 520 } 521 SkConvertPixels(SkImageInfo::MakeA8(pmap.width(), pmap.height()), alpha, alphaRowBytes, 522 pmap.info(), pmap.addr(), pmap.rowBytes(), nullptr, 523 SkTransferFunctionBehavior::kRespect); 524 return true; 525} 526 527#include "SkPaint.h" 528#include "SkMaskFilter.h" 529#include "SkMatrix.h" 530 531bool SkBitmap::extractAlpha(SkBitmap* dst, const SkPaint* paint, 532 Allocator *allocator, SkIPoint* offset) const { 533 SkDEBUGCODE(this->validate();) 534 535 SkBitmap tmpBitmap; 536 SkMatrix identity; 537 SkMask srcM, dstM; 538 539 srcM.fBounds.set(0, 0, this->width(), this->height()); 540 srcM.fRowBytes = SkAlign4(this->width()); 541 srcM.fFormat = SkMask::kA8_Format; 542 543 SkMaskFilter* filter = paint ? paint->getMaskFilter() : nullptr; 544 545 // compute our (larger?) dst bounds if we have a filter 546 if (filter) { 547 identity.reset(); 548 if (!as_MFB(filter)->filterMask(&dstM, srcM, identity, nullptr)) { 549 goto NO_FILTER_CASE; 550 } 551 dstM.fRowBytes = SkAlign4(dstM.fBounds.width()); 552 } else { 553 NO_FILTER_CASE: 554 tmpBitmap.setInfo(SkImageInfo::MakeA8(this->width(), this->height()), srcM.fRowBytes); 555 if (!tmpBitmap.tryAllocPixels(allocator)) { 556 // Allocation of pixels for alpha bitmap failed. 557 SkDebugf("extractAlpha failed to allocate (%d,%d) alpha bitmap\n", 558 tmpBitmap.width(), tmpBitmap.height()); 559 return false; 560 } 561 GetBitmapAlpha(*this, tmpBitmap.getAddr8(0, 0), srcM.fRowBytes); 562 if (offset) { 563 offset->set(0, 0); 564 } 565 tmpBitmap.swap(*dst); 566 return true; 567 } 568 srcM.fImage = SkMask::AllocImage(srcM.computeImageSize()); 569 SkAutoMaskFreeImage srcCleanup(srcM.fImage); 570 571 GetBitmapAlpha(*this, srcM.fImage, srcM.fRowBytes); 572 if (!as_MFB(filter)->filterMask(&dstM, srcM, identity, nullptr)) { 573 goto NO_FILTER_CASE; 574 } 575 SkAutoMaskFreeImage dstCleanup(dstM.fImage); 576 577 tmpBitmap.setInfo(SkImageInfo::MakeA8(dstM.fBounds.width(), dstM.fBounds.height()), 578 dstM.fRowBytes); 579 if (!tmpBitmap.tryAllocPixels(allocator)) { 580 // Allocation of pixels for alpha bitmap failed. 581 SkDebugf("extractAlpha failed to allocate (%d,%d) alpha bitmap\n", 582 tmpBitmap.width(), tmpBitmap.height()); 583 return false; 584 } 585 memcpy(tmpBitmap.getPixels(), dstM.fImage, dstM.computeImageSize()); 586 if (offset) { 587 offset->set(dstM.fBounds.fLeft, dstM.fBounds.fTop); 588 } 589 SkDEBUGCODE(tmpBitmap.validate();) 590 591 tmpBitmap.swap(*dst); 592 return true; 593} 594 595/////////////////////////////////////////////////////////////////////////////// 596 597#ifdef SK_DEBUG 598void SkBitmap::validate() const { 599 this->info().validate(); 600 601 // ImageInfo may not require this, but Bitmap ensures that opaque-only 602 // colorTypes report opaque for their alphatype 603 if (kRGB_565_SkColorType == this->colorType()) { 604 SkASSERT(kOpaque_SkAlphaType == this->alphaType()); 605 } 606 607 SkASSERT(this->info().validRowBytes(this->rowBytes())); 608 uint8_t allFlags = kImageIsVolatile_Flag; 609#ifdef SK_BUILD_FOR_ANDROID 610 allFlags |= kHasHardwareMipMap_Flag; 611#endif 612 SkASSERT((~allFlags & fFlags) == 0); 613 614 if (fPixelRef && fPixelRef->pixels()) { 615 SkASSERT(this->getPixels()); 616 } else { 617 SkASSERT(!this->getPixels()); 618 } 619 620 if (this->getPixels()) { 621 SkASSERT(fPixelRef); 622 SkASSERT(fPixelRef->rowBytes() == this->rowBytes()); 623 SkIPoint origin = this->pixelRefOrigin(); 624 SkASSERT(origin.fX >= 0); 625 SkASSERT(origin.fY >= 0); 626 SkASSERT(fPixelRef->width() >= (int)this->width() + origin.fX); 627 SkASSERT(fPixelRef->height() >= (int)this->height() + origin.fY); 628 SkASSERT(fPixelRef->rowBytes() >= this->info().minRowBytes()); 629 } 630} 631#endif 632 633#ifndef SK_IGNORE_TO_STRING 634#include "SkString.h" 635void SkBitmap::toString(SkString* str) const { 636 637 static const char* gColorTypeNames[kLastEnum_SkColorType + 1] = { 638 "UNKNOWN", "A8", "565", "4444", "RGBA", "BGRA", "INDEX8", 639 }; 640 641 str->appendf("bitmap: ((%d, %d) %s", this->width(), this->height(), 642 gColorTypeNames[this->colorType()]); 643 644 str->append(" ("); 645 if (this->isOpaque()) { 646 str->append("opaque"); 647 } else { 648 str->append("transparent"); 649 } 650 if (this->isImmutable()) { 651 str->append(", immutable"); 652 } else { 653 str->append(", not-immutable"); 654 } 655 str->append(")"); 656 657 str->appendf(" pixelref:%p", this->pixelRef()); 658 str->append(")"); 659} 660#endif 661 662/////////////////////////////////////////////////////////////////////////////// 663 664bool SkBitmap::peekPixels(SkPixmap* pmap) const { 665 if (this->getPixels()) { 666 if (pmap) { 667 *pmap = fPixmap; 668 } 669 return true; 670 } 671 return false; 672} 673 674/////////////////////////////////////////////////////////////////////////////// 675 676#ifdef SK_DEBUG 677void SkImageInfo::validate() const { 678 SkASSERT(fWidth >= 0); 679 SkASSERT(fHeight >= 0); 680 SkASSERT(SkColorTypeIsValid(fColorType)); 681 SkASSERT(SkAlphaTypeIsValid(fAlphaType)); 682} 683#endif 684