1/*
2 * Copyright 2012 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 "include/core/SkBitmap.h"
9#include "include/core/SkCanvas.h"
10#include "include/core/SkData.h"
11#include "include/core/SkImageEncoder.h"
12#include "include/core/SkImageFilter.h"
13#include "include/core/SkImageGenerator.h"
14#include "include/core/SkPicture.h"
15#include "include/core/SkString.h"
16#include "include/core/SkSurface.h"
17#include "src/core/SkBitmapCache.h"
18#include "src/core/SkCachedData.h"
19#include "src/core/SkColorSpacePriv.h"
20#include "src/core/SkImageFilterCache.h"
21#include "src/core/SkImageFilter_Base.h"
22#include "src/core/SkImagePriv.h"
23#include "src/core/SkMipmap.h"
24#include "src/core/SkNextID.h"
25#include "src/core/SkSpecialImage.h"
26#include "src/image/SkImage_Base.h"
27#include "src/image/SkReadPixelsRec.h"
28#include "src/image/SkRescaleAndReadPixels.h"
29#include "src/shaders/SkImageShader.h"
30
31#if SK_SUPPORT_GPU
32#include "include/gpu/GrDirectContext.h"
33#include "src/gpu/GrContextPriv.h"
34#include "src/image/SkImage_Gpu.h"
35#endif
36#include "include/gpu/GrBackendSurface.h"
37
38SkImage::SkImage(const SkImageInfo& info, uint32_t uniqueID)
39 : fInfo(info)
40 , fUniqueID(kNeedNewImageUniqueID == uniqueID ? SkNextID::ImageID() : uniqueID) {
41 SkASSERT(info.width() > 0);
42 SkASSERT(info.height() > 0);
43}
44
45bool SkImage::peekPixels(SkPixmap* pm) const {
46 SkPixmap tmp;
47 if (!pm) {
48 pm = &tmp;
49 }
50 return as_IB(this)->onPeekPixels(pm);
51}
52
53bool SkImage::readPixels(const SkImageInfo& dstInfo, void* dstPixels, size_t dstRowBytes, int srcX,
54 int srcY, CachingHint chint) const {
55 return as_IB(this)->onReadPixels(dstInfo, dstPixels, dstRowBytes, srcX, srcY, chint);
56}
57
58void SkImage::asyncRescaleAndReadPixels(const SkImageInfo& info,
59 const SkIRect& srcRect,
60 RescaleGamma rescaleGamma,
61 SkFilterQuality rescaleQuality,
62 ReadPixelsCallback callback,
63 ReadPixelsContext context) {
64 if (!SkIRect::MakeWH(this->width(), this->height()).contains(srcRect) ||
65 !SkImageInfoIsValid(info)) {
66 callback(context, nullptr);
67 return;
68 }
69 as_IB(this)->onAsyncRescaleAndReadPixels(
70 info, srcRect, rescaleGamma, rescaleQuality, callback, context);
71}
72
73void SkImage::asyncRescaleAndReadPixelsYUV420(SkYUVColorSpace yuvColorSpace,
74 sk_sp<SkColorSpace> dstColorSpace,
75 const SkIRect& srcRect,
76 const SkISize& dstSize,
77 RescaleGamma rescaleGamma,
78 SkFilterQuality rescaleQuality,
79 ReadPixelsCallback callback,
80 ReadPixelsContext context) {
81 if (!SkIRect::MakeWH(this->width(), this->height()).contains(srcRect) || dstSize.isZero() ||
82 (dstSize.width() & 0b1) || (dstSize.height() & 0b1)) {
83 callback(context, nullptr);
84 return;
85 }
86 as_IB(this)->onAsyncRescaleAndReadPixelsYUV420(yuvColorSpace,
87 std::move(dstColorSpace),
88 srcRect,
89 dstSize,
90 rescaleGamma,
91 rescaleQuality,
92 callback,
93 context);
94}
95
96bool SkImage::scalePixels(const SkPixmap& dst, SkFilterQuality quality, CachingHint chint) const {
97 if (this->width() == dst.width() && this->height() == dst.height()) {
98 return this->readPixels(dst, 0, 0, chint);
99 }
100
101 // Idea: If/when SkImageGenerator supports a native-scaling API (where the generator itself
102 // can scale more efficiently) we should take advantage of it here.
103 //
104 SkBitmap bm;
105 if (as_IB(this)->getROPixels(&bm, chint)) {
106 SkPixmap pmap;
107 // Note: By calling the pixmap scaler, we never cache the final result, so the chint
108 // is (currently) only being applied to the getROPixels. If we get a request to
109 // also attempt to cache the final (scaled) result, we would add that logic here.
110 //
111 return bm.peekPixels(&pmap) && pmap.scalePixels(dst, quality);
112 }
113 return false;
114}
115
116///////////////////////////////////////////////////////////////////////////////////////////////////
117
118SkColorType SkImage::colorType() const { return fInfo.colorType(); }
119
120SkAlphaType SkImage::alphaType() const { return fInfo.alphaType(); }
121
122SkColorSpace* SkImage::colorSpace() const { return fInfo.colorSpace(); }
123
124sk_sp<SkColorSpace> SkImage::refColorSpace() const { return fInfo.refColorSpace(); }
125
126sk_sp<SkShader> SkImage::makeShader(SkTileMode tmx, SkTileMode tmy,
127 const SkMatrix* localMatrix) const {
128 return SkImageShader::Make(sk_ref_sp(const_cast<SkImage*>(this)), tmx, tmy, localMatrix,
129 SkImageShader::kInheritFromPaint);
130}
131
132sk_sp<SkShader> SkImage::makeShader(SkTileMode tmx, SkTileMode tmy,
133 const SkFilterOptions& options,
134 const SkMatrix* localMatrix) const {
135 return SkImageShader::Make(sk_ref_sp(const_cast<SkImage*>(this)), tmx, tmy,
136 options, localMatrix);
137}
138
139sk_sp<SkShader> SkImage::makeShader(SkTileMode tmx, SkTileMode tmy,
140 const SkMatrix* localMatrix, SkFilterQuality filtering) const {
141 return SkImageShader::Make(sk_ref_sp(const_cast<SkImage*>(this)), tmx, tmy, localMatrix,
142 SkImageShader::FilterEnum(filtering));
143}
144
145sk_sp<SkShader> SkImage::makeShader(SkTileMode tmx, SkTileMode tmy, CubicResampler cubic,
146 const SkMatrix* localMatrix) const {
147 return SkImageShader::Make(sk_ref_sp(const_cast<SkImage*>(this)), tmx, tmy, cubic,
148 localMatrix);
149}
150
151sk_sp<SkData> SkImage::encodeToData(SkEncodedImageFormat type, int quality) const {
152 SkBitmap bm;
153 if (as_IB(this)->getROPixels(&bm)) {
154 return SkEncodeBitmap(bm, type, quality);
155 }
156 return nullptr;
157}
158
159sk_sp<SkData> SkImage::encodeToData() const {
160 if (auto encoded = this->refEncodedData()) {
161 return encoded;
162 }
163
164 return this->encodeToData(SkEncodedImageFormat::kPNG, 100);
165}
166
167sk_sp<SkData> SkImage::refEncodedData() const {
168 return sk_sp<SkData>(as_IB(this)->onRefEncoded());
169}
170
171sk_sp<SkImage> SkImage::MakeFromEncoded(sk_sp<SkData> encoded) {
172 if (nullptr == encoded || 0 == encoded->size()) {
173 return nullptr;
174 }
175 return SkImage::MakeFromGenerator(SkImageGenerator::MakeFromEncoded(std::move(encoded)));
176}
177
178///////////////////////////////////////////////////////////////////////////////////////////////////
179
180sk_sp<SkImage> SkImage::makeSubset(const SkIRect& subset, GrDirectContext* direct) const {
181 if (subset.isEmpty()) {
182 return nullptr;
183 }
184
185 const SkIRect bounds = SkIRect::MakeWH(this->width(), this->height());
186 if (!bounds.contains(subset)) {
187 return nullptr;
188 }
189
190#if SK_SUPPORT_GPU
191 auto myContext = as_IB(this)->context();
192 // This check is also performed in the subclass, but we do it here for the short-circuit below.
193 if (myContext && !myContext->priv().matches(direct)) {
194 return nullptr;
195 }
196#endif
197
198 // optimization : return self if the subset == our bounds
199 if (bounds == subset) {
200 return sk_ref_sp(const_cast<SkImage*>(this));
201 }
202
203 return as_IB(this)->onMakeSubset(subset, direct);
204}
205
206#if SK_SUPPORT_GPU
207
208bool SkImage::isTextureBacked() const { return as_IB(this)->onIsTextureBacked(); }
209
210GrBackendTexture SkImage::getBackendTexture(bool flushPendingGrContextIO,
211 GrSurfaceOrigin* origin) const {
212 return as_IB(this)->onGetBackendTexture(flushPendingGrContextIO, origin);
213}
214
215bool SkImage::isValid(GrRecordingContext* rContext) const {
216 if (rContext && rContext->abandoned()) {
217 return false;
218 }
219 return as_IB(this)->onIsValid(rContext);
220}
221
222GrSemaphoresSubmitted SkImage::flush(GrDirectContext* dContext, const GrFlushInfo& flushInfo) {
223 return as_IB(this)->onFlush(dContext, flushInfo);
224}
225
226void SkImage::flushAndSubmit(GrDirectContext* dContext) {
227 this->flush(dContext, {});
228 dContext->submit();
229}
230
231#else
232
233bool SkImage::isTextureBacked() const { return false; }
234
235GrBackendTexture SkImage::getBackendTexture(bool flushPendingGrContextIO,
236 GrSurfaceOrigin* origin) const {
237 return GrBackendTexture(); // invalid
238}
239
240bool SkImage::isValid(GrRecordingContext* rContext) const {
241 if (rContext) {
242 return false;
243 }
244 return as_IB(this)->onIsValid(nullptr);
245}
246
247GrSemaphoresSubmitted SkImage::flush(GrDirectContext*, const GrFlushInfo&) {
248 return GrSemaphoresSubmitted::kNo;
249}
250
251void SkImage::flushAndSubmit(GrDirectContext*) {}
252
253#endif
254
255///////////////////////////////////////////////////////////////////////////////
256
257SkImage_Base::SkImage_Base(const SkImageInfo& info, uint32_t uniqueID)
258 : INHERITED(info, uniqueID), fAddedToRasterCache(false) {}
259
260SkImage_Base::~SkImage_Base() {
261 if (fAddedToRasterCache.load()) {
262 SkNotifyBitmapGenIDIsStale(this->uniqueID());
263 }
264}
265
266void SkImage_Base::onAsyncRescaleAndReadPixels(const SkImageInfo& info,
267 const SkIRect& origSrcRect,
268 RescaleGamma rescaleGamma,
269 SkFilterQuality rescaleQuality,
270 ReadPixelsCallback callback,
271 ReadPixelsContext context) {
272 SkBitmap src;
273 SkPixmap peek;
274 SkIRect srcRect;
275 if (this->peekPixels(&peek)) {
276 src.installPixels(peek);
277 srcRect = origSrcRect;
278 } else {
279 src.setInfo(this->imageInfo().makeDimensions(origSrcRect.size()));
280 src.allocPixels();
281 if (!this->readPixels(src.pixmap(), origSrcRect.x(), origSrcRect.y())) {
282 callback(context, nullptr);
283 return;
284 }
285 srcRect = SkIRect::MakeSize(src.dimensions());
286 }
287 return SkRescaleAndReadPixels(
288 src, info, srcRect, rescaleGamma, rescaleQuality, callback, context);
289}
290
291void SkImage_Base::onAsyncRescaleAndReadPixelsYUV420(SkYUVColorSpace,
292 sk_sp<SkColorSpace> dstColorSpace,
293 const SkIRect& srcRect,
294 const SkISize& dstSize,
295 RescaleGamma,
296 SkFilterQuality,
297 ReadPixelsCallback callback,
298 ReadPixelsContext context) {
299 // TODO: Call non-YUV asyncRescaleAndReadPixels and then make our callback convert to YUV and
300 // call client's callback.
301 callback(context, nullptr);
302}
303
304GrBackendTexture SkImage_Base::onGetBackendTexture(bool flushPendingGrContextIO,
305 GrSurfaceOrigin* origin) const {
306 return GrBackendTexture(); // invalid
307}
308
309bool SkImage::readPixels(const SkPixmap& pmap, int srcX, int srcY, CachingHint chint) const {
310 return this->readPixels(pmap.info(), pmap.writable_addr(), pmap.rowBytes(), srcX, srcY, chint);
311}
312
313///////////////////////////////////////////////////////////////////////////////////////////////////
314
315sk_sp<SkImage> SkImage::MakeFromBitmap(const SkBitmap& bm) {
316 if (!bm.pixelRef()) {
317 return nullptr;
318 }
319
320 return SkMakeImageFromRasterBitmap(bm, kIfMutable_SkCopyPixelsMode);
321}
322
323bool SkImage::asLegacyBitmap(SkBitmap* bitmap, LegacyBitmapMode ) const {
324 return as_IB(this)->onAsLegacyBitmap(bitmap);
325}
326
327bool SkImage_Base::onAsLegacyBitmap(SkBitmap* bitmap) const {
328 // As the base-class, all we can do is make a copy (regardless of mode).
329 // Subclasses that want to be more optimal should override.
330 SkImageInfo info = fInfo.makeColorType(kN32_SkColorType).makeColorSpace(nullptr);
331 if (!bitmap->tryAllocPixels(info)) {
332 return false;
333 }
334 if (!this->readPixels(bitmap->info(), bitmap->getPixels(), bitmap->rowBytes(), 0, 0)) {
335 bitmap->reset();
336 return false;
337 }
338
339 bitmap->setImmutable();
340 return true;
341}
342
343sk_sp<SkImage> SkImage::MakeFromPicture(sk_sp<SkPicture> picture, const SkISize& dimensions,
344 const SkMatrix* matrix, const SkPaint* paint,
345 BitDepth bitDepth, sk_sp<SkColorSpace> colorSpace) {
346 return MakeFromGenerator(SkImageGenerator::MakeFromPicture(dimensions, std::move(picture),
347 matrix, paint, bitDepth,
348 std::move(colorSpace)));
349}
350
351sk_sp<SkImage> SkImage::makeWithFilter(GrRecordingContext* rContext, const SkImageFilter* filter,
352 const SkIRect& subset, const SkIRect& clipBounds,
353 SkIRect* outSubset, SkIPoint* offset) const {
354
355 if (!filter || !outSubset || !offset || !this->bounds().contains(subset)) {
356 return nullptr;
357 }
358 sk_sp<SkSpecialImage> srcSpecialImage;
359#if SK_SUPPORT_GPU
360 auto myContext = as_IB(this)->context();
361 if (myContext && !myContext->priv().matches(rContext)) {
362 return nullptr;
363 }
364 srcSpecialImage = SkSpecialImage::MakeFromImage(rContext, subset,
365 sk_ref_sp(const_cast<SkImage*>(this)));
366#else
367 srcSpecialImage = SkSpecialImage::MakeFromImage(nullptr, subset,
368 sk_ref_sp(const_cast<SkImage*>(this)));
369#endif
370 if (!srcSpecialImage) {
371 return nullptr;
372 }
373
374 sk_sp<SkImageFilterCache> cache(
375 SkImageFilterCache::Create(SkImageFilterCache::kDefaultTransientSize));
376
377 // The filters operate in the local space of the src image, where (0,0) corresponds to the
378 // subset's top left corner. But the clip bounds and any crop rects on the filters are in the
379 // original coordinate system, so configure the CTM to correct crop rects and explicitly adjust
380 // the clip bounds (since it is assumed to already be in image space).
381 SkImageFilter_Base::Context context(SkMatrix::Translate(-subset.x(), -subset.y()),
382 clipBounds.makeOffset(-subset.topLeft()),
383 cache.get(), fInfo.colorType(), fInfo.colorSpace(),
384 srcSpecialImage.get());
385
386 sk_sp<SkSpecialImage> result = as_IFB(filter)->filterImage(context).imageAndOffset(offset);
387 if (!result) {
388 return nullptr;
389 }
390
391 // The output image and offset are relative to the subset rectangle, so the offset needs to
392 // be shifted to put it in the correct spot with respect to the original coordinate system
393 offset->fX += subset.x();
394 offset->fY += subset.y();
395
396 // Final clip against the exact clipBounds (the clip provided in the context gets adjusted
397 // to account for pixel-moving filters so doesn't always exactly match when finished). The
398 // clipBounds are translated into the clippedDstRect coordinate space, including the
399 // result->subset() ensures that the result's image pixel origin does not affect results.
400 SkIRect dstRect = result->subset();
401 SkIRect clippedDstRect = dstRect;
402 if (!clippedDstRect.intersect(clipBounds.makeOffset(result->subset().topLeft() - *offset))) {
403 return nullptr;
404 }
405
406 // Adjust the geometric offset if the top-left corner moved as well
407 offset->fX += (clippedDstRect.x() - dstRect.x());
408 offset->fY += (clippedDstRect.y() - dstRect.y());
409 *outSubset = clippedDstRect;
410 return result->asImage();
411}
412
413bool SkImage::isLazyGenerated() const {
414 return as_IB(this)->onIsLazyGenerated();
415}
416
417bool SkImage::isAlphaOnly() const { return SkColorTypeIsAlphaOnly(fInfo.colorType()); }
418
419sk_sp<SkImage> SkImage::makeColorSpace(sk_sp<SkColorSpace> target, GrDirectContext* direct) const {
420 return this->makeColorTypeAndColorSpace(this->colorType(), std::move(target), direct);
421}
422
423sk_sp<SkImage> SkImage::makeColorTypeAndColorSpace(SkColorType targetColorType,
424 sk_sp<SkColorSpace> targetColorSpace,
425 GrDirectContext* dContext) const {
426 if (kUnknown_SkColorType == targetColorType || !targetColorSpace) {
427 return nullptr;
428 }
429
430#if SK_SUPPORT_GPU
431 auto myContext = as_IB(this)->context();
432 // This check is also performed in the subclass, but we do it here for the short-circuit below.
433 if (myContext && !myContext->priv().matches(dContext)) {
434 return nullptr;
435 }
436#endif
437
438 SkColorType colorType = this->colorType();
439 SkColorSpace* colorSpace = this->colorSpace();
440 if (!colorSpace) {
441 colorSpace = sk_srgb_singleton();
442 }
443 if (colorType == targetColorType &&
444 (SkColorSpace::Equals(colorSpace, targetColorSpace.get()) || this->isAlphaOnly())) {
445 return sk_ref_sp(const_cast<SkImage*>(this));
446 }
447
448 return as_IB(this)->onMakeColorTypeAndColorSpace(targetColorType,
449 std::move(targetColorSpace), dContext);
450}
451
452sk_sp<SkImage> SkImage::reinterpretColorSpace(sk_sp<SkColorSpace> target) const {
453 if (!target) {
454 return nullptr;
455 }
456
457 // No need to create a new image if:
458 // (1) The color spaces are equal.
459 // (2) The color type is kAlpha8.
460 SkColorSpace* colorSpace = this->colorSpace();
461 if (!colorSpace) {
462 colorSpace = sk_srgb_singleton();
463 }
464 if (SkColorSpace::Equals(colorSpace, target.get()) || this->isAlphaOnly()) {
465 return sk_ref_sp(const_cast<SkImage*>(this));
466 }
467
468 return as_IB(this)->onReinterpretColorSpace(std::move(target));
469}
470
471sk_sp<SkImage> SkImage::makeNonTextureImage() const {
472 if (!this->isTextureBacked()) {
473 return sk_ref_sp(const_cast<SkImage*>(this));
474 }
475 return this->makeRasterImage();
476}
477
478sk_sp<SkImage> SkImage::makeRasterImage(CachingHint chint) const {
479 SkPixmap pm;
480 if (this->peekPixels(&pm)) {
481 return sk_ref_sp(const_cast<SkImage*>(this));
482 }
483
484 const size_t rowBytes = fInfo.minRowBytes();
485 size_t size = fInfo.computeByteSize(rowBytes);
486 if (SkImageInfo::ByteSizeOverflowed(size)) {
487 return nullptr;
488 }
489
490 sk_sp<SkData> data = SkData::MakeUninitialized(size);
491 pm = {fInfo.makeColorSpace(nullptr), data->writable_data(), fInfo.minRowBytes()};
492 if (!this->readPixels(pm, 0, 0, chint)) {
493 return nullptr;
494 }
495
496 return SkImage::MakeRasterData(fInfo, std::move(data), rowBytes);
497}
498
499//////////////////////////////////////////////////////////////////////////////////////
500
501#if !SK_SUPPORT_GPU
502
503sk_sp<SkImage> SkImage::MakeFromTexture(GrRecordingContext*,
504 const GrBackendTexture&, GrSurfaceOrigin,
505 SkColorType, SkAlphaType, sk_sp<SkColorSpace>,
506 TextureReleaseProc, ReleaseContext) {
507 return nullptr;
508}
509
510sk_sp<SkImage> SkImage::MakeFromCompressedTexture(GrRecordingContext*,
511 const GrBackendTexture&,
512 GrSurfaceOrigin,
513 SkAlphaType,
514 sk_sp<SkColorSpace>,
515 TextureReleaseProc,
516 ReleaseContext) {
517 return nullptr;
518}
519
520bool SkImage::MakeBackendTextureFromSkImage(GrContext*,
521 sk_sp<SkImage>,
522 GrBackendTexture*,
523 BackendTextureReleaseProc*) {
524 return false;
525}
526
527sk_sp<SkImage> SkImage::MakeFromAdoptedTexture(GrRecordingContext*,
528 const GrBackendTexture&, GrSurfaceOrigin,
529 SkColorType, SkAlphaType,
530 sk_sp<SkColorSpace>) {
531 return nullptr;
532}
533
534sk_sp<SkImage> SkImage::MakeFromYUVATexturesCopy(GrRecordingContext*,
535 SkYUVColorSpace,
536 const GrBackendTexture[],
537 const SkYUVAIndex[4],
538 SkISize,
539 GrSurfaceOrigin,
540 sk_sp<SkColorSpace>) {
541 return nullptr;
542}
543
544sk_sp<SkImage> SkImage::MakeFromYUVATexturesCopyWithExternalBackend(
545 GrRecordingContext*,
546 SkYUVColorSpace,
547 const GrBackendTexture[],
548 const SkYUVAIndex[4],
549 SkISize,
550 GrSurfaceOrigin,
551 const GrBackendTexture&,
552 sk_sp<SkColorSpace>,
553 TextureReleaseProc,
554 ReleaseContext) {
555 return nullptr;
556}
557
558sk_sp<SkImage> SkImage::MakeFromYUVTexturesCopyWithExternalBackend(
559 GrContext*, SkYUVColorSpace, const GrBackendTexture[3], GrSurfaceOrigin,
560 const GrBackendTexture&, sk_sp<SkColorSpace>) {
561 return nullptr;
562}
563
564sk_sp<SkImage> SkImage::MakeFromNV12TexturesCopy(GrContext*, SkYUVColorSpace,
565 const GrBackendTexture[2],
566 GrSurfaceOrigin, sk_sp<SkColorSpace>) {
567 return nullptr;
568}
569
570
571sk_sp<SkImage> SkImage::makeTextureImage(GrDirectContext*, GrMipmapped, SkBudgeted) const {
572 return nullptr;
573}
574
575sk_sp<SkImage> SkImage::MakeFromNV12TexturesCopyWithExternalBackend(
576 GrContext*, SkYUVColorSpace, const GrBackendTexture[2], GrSurfaceOrigin,
577 const GrBackendTexture&, sk_sp<SkColorSpace>, TextureReleaseProc, ReleaseContext) {
578 return nullptr;
579}
580
581#endif
582
583///////////////////////////////////////////////////////////////////////////////////////////////////
584
585bool SkImage_pinAsTexture(const SkImage* image, GrRecordingContext* rContext) {
586 SkASSERT(image);
587 SkASSERT(rContext);
588 return as_IB(image)->onPinAsTexture(rContext);
589}
590
591void SkImage_unpinAsTexture(const SkImage* image, GrRecordingContext* rContext) {
592 SkASSERT(image);
593 SkASSERT(rContext);
594 as_IB(image)->onUnpinAsTexture(rContext);
595}
596
597///////////////////////////////////////////////////////////////////////////////////////////////////
598
599SkMipmapBuilder::SkMipmapBuilder(const SkImageInfo& info) {
600 fMM = sk_sp<SkMipmap>(SkMipmap::Build({info, nullptr, 0}, nullptr, false));
601}
602
603SkMipmapBuilder::~SkMipmapBuilder() {}
604
605int SkMipmapBuilder::countLevels() const {
606 return fMM ? fMM->countLevels() : 0;
607}
608
609SkPixmap SkMipmapBuilder::level(int index) const {
610 SkPixmap pm;
611
612 SkMipmap::Level level;
613 if (fMM && fMM->getLevel(index, &level)) {
614 pm = level.fPixmap;
615 }
616 return pm;
617}
618
619sk_sp<SkMipmap> SkMipmapBuilder::detach() {
620 return std::move(fMM);
621}
622
623bool SkImage::hasMipmaps() const {
624 return as_IB(this)->onPeekMips() != nullptr;
625}
626
627sk_sp<SkImage> SkImage::withMipmaps(sk_sp<SkMipmap> mips) const {
628 if (mips == nullptr || mips->validForRootLevel(this->imageInfo())) {
629 if (auto result = as_IB(this)->onMakeWithMipmaps(std::move(mips))) {
630 return result;
631 }
632 }
633 return sk_ref_sp((const_cast<SkImage*>(this)));
634}
635