check-point for surface experiment
authorreed@google.com <reed@google.com@2bbb7eff-a529-9590-31e7-b0007b416f81>
Fri, 27 Jul 2012 21:10:42 +0000 (21:10 +0000)
committerreed@google.com <reed@google.com@2bbb7eff-a529-9590-31e7-b0007b416f81>
Fri, 27 Jul 2012 21:10:42 +0000 (21:10 +0000)
git-svn-id: http://skia.googlecode.com/svn/trunk@4819 2bbb7eff-a529-9590-31e7-b0007b416f81

include/core/SkImage.h
include/core/SkSurface.h [new file with mode: 0644]
src/image/SkImage.cpp
src/image/SkImagePriv.cpp [new file with mode: 0644]
src/image/SkImagePriv.h [new file with mode: 0644]
src/image/SkSurface.cpp [new file with mode: 0644]

index df71875..ebe275e 100644 (file)
@@ -93,63 +93,4 @@ private:
     static uint32_t NextUniqueID();
 };
 
-/**
- *  SkSurface represents the backend/results of drawing to a canvas. For raster
- *  drawing, the surface will be pixels, but (for example) when drawing into
- *  a PDF or Picture canvas, the surface stores the recorded commands.
- *
- *  To draw into a canvas, first create the appropriate type of Surface, and
- *  then request the canvas from the surface.
- */
-class SkSurface : public SkRefCnt {
-public:
-    static SkSurface*   NewRasterDirect(const SkImage::Info&, SkColorSpace*,
-                                        const void* pixels, size_t rowBytes);
-    static SkSurface*   NewRaster(const SkImage::Info&, SkColorSpace*);
-    static SkSurface*   NewGpu(GrContext*);
-    static SkSurface*   NewPDF(...);
-    static SkSurface*   NewXPS(...);
-    static SkSurface*   NewPicture(int width, int height);
-
-    /**
-     *  Return a canvas that will draw into this surface.
-     *
-     *  LIFECYCLE QUESTIONS
-     *  1. Is this owned by the surface or the caller?
-     *  2. Can the caller get a 2nd canvas, or reset the state of the first?
-     */
-    SkCanvas* newCanvas();
-
-    /**
-     *  Return a new surface that is "compatible" with this one, in that it will
-     *  efficiently be able to be drawn into this surface. Typical calling
-     *  pattern:
-     *
-     *  SkSurface* A = SkSurface::New...();
-     *  SkCanvas* canvasA = surfaceA->newCanvas();
-     *  ...
-     *  SkSurface* surfaceB = surfaceA->newSurface(...);
-     *  SkCanvas* canvasB = surfaceB->newCanvas();
-     *  ... // draw using canvasB
-     *  canvasA->drawSurface(surfaceB); // <--- this will always be optimal!
-     */
-    SkSurface* newSurface(int width, int height);
-
-    /**
-     *  Returns an image of the current state of the surface pixels up to this
-     *  point. Subsequent changes to the surface (by drawing into its canvas)
-     *  will not be reflected in this image.
-     */
-    SkImage* newImageShapshot();
-
-    /**
-     *  Thought the caller could get a snapshot image explicitly, and draw that,
-     *  it seems that directly drawing a surface into another canvas might be
-     *  a common pattern, and that we could possibly be more efficient, since
-     *  we'd know that the "snapshot" need only live until we've handed it off
-     *  to the canvas.
-     */
-    void draw(SkCanvas*, SkScalar x, SkScalar y, const SkPaint*);
-};
-
 #endif
diff --git a/include/core/SkSurface.h b/include/core/SkSurface.h
new file mode 100644 (file)
index 0000000..b399a2f
--- /dev/null
@@ -0,0 +1,122 @@
+/*
+ * Copyright 2012 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef SkSurface_DEFINED
+#define SkSurface_DEFINED
+
+#include "SkRefCnt.h"
+#include "SkImage.h"
+
+class SkCanvas;
+class SkPaint;
+
+/**
+ *  SkSurface represents the backend/results of drawing to a canvas. For raster
+ *  drawing, the surface will be pixels, but (for example) when drawing into
+ *  a PDF or Picture canvas, the surface stores the recorded commands.
+ *
+ *  To draw into a canvas, first create the appropriate type of Surface, and
+ *  then request the canvas from the surface.
+ */
+class SkSurface : public SkRefCnt {
+public:
+    /**
+     *  Create a new surface, using the specified pixels/rowbytes as its
+     *  backend.
+     *
+     *  If the requested surface cannot be created, or the request is not a
+     *  supported configuration, NULL will be returned.
+     */
+    static SkSurface* NewRasterDirect(const SkImage::Info&, SkColorSpace*,
+                                      void* pixels, size_t rowBytes);
+
+    /**
+     *  Return a new surface, with the memory for the pixels automatically
+     *  allocated.
+     *
+     *  If the requested surface cannot be created, or the request is not a
+     *  supported configuration, NULL will be returned.
+     */
+    static SkSurface* NewRaster(const SkImage::Info&, SkColorSpace*);
+
+    /**
+     *  Return a new surface whose contents will be recorded into a picture.
+     *  When this surface is drawn into another canvas, its contents will be
+     *  "replayed" into that canvas.
+     */
+    static SkSurface* NewPicture(int width, int height);
+
+    int width() const { return fWidth; }
+    int height() const { return fHeight; }
+
+    /**
+     *  Returns a unique non-zero, unique value identifying the content of this
+     *  surface. Each time the content is changed changed, either by drawing
+     *  into this surface, or explicitly calling notifyContentChanged()) this
+     *  method will return a new value.
+     *
+     *  If this surface is empty (i.e. has a zero-dimention), this will return
+     *  0.
+     */
+    uint32_t generationID() const;
+    
+    /**
+     *  Call this if the contents have changed. This will (lazily) force a new
+     *  value to be returned from generationID() when it is called next.
+     */
+    void notifyContentChanged();
+    
+    /**
+     *  Return a canvas that will draw into this surface.
+     *
+     *  LIFECYCLE QUESTIONS
+     *  1. Is this owned by the surface or the caller?
+     *  2. Can the caller get a 2nd canvas, or reset the state of the first?
+     */
+    SkCanvas* newCanvas();
+
+    /**
+     *  Return a new surface that is "compatible" with this one, in that it will
+     *  efficiently be able to be drawn into this surface. Typical calling
+     *  pattern:
+     *
+     *  SkSurface* A = SkSurface::New...();
+     *  SkCanvas* canvasA = surfaceA->newCanvas();
+     *  ...
+     *  SkSurface* surfaceB = surfaceA->newSurface(...);
+     *  SkCanvas* canvasB = surfaceB->newCanvas();
+     *  ... // draw using canvasB
+     *  canvasA->drawSurface(surfaceB); // <--- this will always be optimal!
+     */
+    SkSurface* newSurface(const SkImage::Info&, SkColorSpace*);
+
+    /**
+     *  Returns an image of the current state of the surface pixels up to this
+     *  point. Subsequent changes to the surface (by drawing into its canvas)
+     *  will not be reflected in this image.
+     */
+    SkImage* newImageShapshot();
+
+    /**
+     *  Thought the caller could get a snapshot image explicitly, and draw that,
+     *  it seems that directly drawing a surface into another canvas might be
+     *  a common pattern, and that we could possibly be more efficient, since
+     *  we'd know that the "snapshot" need only live until we've handed it off
+     *  to the canvas.
+     */
+    void draw(SkCanvas*, SkScalar x, SkScalar y, const SkPaint*);
+
+protected:
+    SkSurface(int width, int height);
+
+private:
+    const int           fWidth;
+    const int           fHeight;
+    mutable uint32_t    fGenerationID;
+};
+
+#endif
index 0573a83..4d96b9f 100644 (file)
@@ -1,5 +1,5 @@
 #include "SkImage.h"
-
+#include "SkImagePriv.h"
 #include "SkBitmap.h"
 
 ///////////////////////////////////////////////////////////////////////////////
@@ -20,70 +20,6 @@ static SkImage_Base* asIB(SkImage* image) {
 
 ///////////////////////////////////////////////////////////////////////////////
 
-static SkBitmap::Config InfoToConfig(const SkImage::Info& info, bool* isOpaque) {
-    switch (info.fColorType) {
-        case SkImage::kAlpha_8_ColorType:
-            switch (info.fAlphaType) {
-                case SkImage::kIgnore_AlphaType:
-                    // makes no sense
-                    return SkBitmap::kNo_Config;
-
-                case SkImage::kOpaque_AlphaType:
-                    *isOpaque = true;
-                    return SkBitmap::kA8_Config;
-
-                case SkImage::kPremul_AlphaType:
-                case SkImage::kUnpremul_AlphaType:
-                    *isOpaque = false;
-                    return SkBitmap::kA8_Config;
-            }
-            break;
-
-        case SkImage::kRGB_565_ColorType:
-            // we ignore fAlpahType, though some would not make sense
-            *isOpaque = true;
-            return SkBitmap::kRGB_565_Config;
-
-        case SkImage::kRGBA_8888_ColorType:
-        case SkImage::kBGRA_8888_ColorType:
-            // not supported yet
-            return SkBitmap::kNo_Config;
-
-        case SkImage::kPMColor_ColorType:
-            switch (info.fAlphaType) {
-                case SkImage::kIgnore_AlphaType:
-                case SkImage::kUnpremul_AlphaType:
-                    // not supported yet
-                    return SkBitmap::kNo_Config;
-                case SkImage::kOpaque_AlphaType:
-                    *isOpaque = true;
-                    return SkBitmap::kARGB_8888_Config;
-                case SkImage::kPremul_AlphaType:
-                    *isOpaque = false;
-                    return SkBitmap::kARGB_8888_Config;
-            }
-            break;
-    }
-    SkASSERT(!"how did we get here");
-    return SkBitmap::kNo_Config;
-}
-
-static int BytesPerPixel(SkImage::ColorType ct) {
-    static const uint8_t gColorTypeBytesPerPixel[] = {
-        1,  // kAlpha_8_ColorType
-        2,  // kRGB_565_ColorType
-        4,  // kRGBA_8888_ColorType
-        4,  // kBGRA_8888_ColorType
-        4,  // kPMColor_ColorType
-    };
-
-    SkASSERT((size_t)ct < SK_ARRAY_COUNT(gColorTypeBytesPerPixel));
-    return gColorTypeBytesPerPixel[ct];
-}
-
-static size_t ComputeMinRowBytes(const SkImage::Info& info) {
-    return info.fWidth * BytesPerPixel(info.fColorType);
-}
 
 class SkImage_Raster : public SkImage_Base {
 public:
@@ -105,13 +41,13 @@ public:
         }
 
         bool isOpaque;
-        if (InfoToConfig(info, &isOpaque) == SkBitmap::kNo_Config) {
+        if (SkImageInfoToBitmapConfig(info, &isOpaque) == SkBitmap::kNo_Config) {
             return false;
         }
             
         // TODO: check colorspace
         
-        if (rowBytes < ComputeMinRowBytes(info)) {
+        if (rowBytes < SkImageMinRowBytes(info)) {
             return false;
         }
         
@@ -129,6 +65,9 @@ public:
 
     virtual const SkBitmap* asABitmap() SK_OVERRIDE;
 
+    // exposed for SkSurface_Raster via SkNewImageFromPixelRef
+    SkImage_Raster(const SkImage::Info&, SkPixelRef*, size_t rowBytes);
+
 private:
     SkImage_Raster() : INHERITED(0, 0) {}
 
@@ -137,6 +76,11 @@ private:
     typedef SkImage_Base INHERITED;
 };
 
+SkImage* SkNewImageFromPixelRef(const SkImage::Info& info, SkPixelRef* pr,
+                                size_t rowBytes) {
+    return SkNEW_ARGS(SkImage_Raster, (info, pr, rowBytes));
+}
+
 ///////////////////////////////////////////////////////////////////////////////
 
 #include "SkData.h"
@@ -154,14 +98,27 @@ SkImage* SkImage_Raster::NewEmpty() {
 
 SkImage_Raster::SkImage_Raster(const Info& info, SkColorSpace* cs,
                                SkData* data, size_t rowBytes)
+: INHERITED(info.fWidth, info.fHeight) {
+    bool isOpaque;
+    SkBitmap::Config config = SkImageInfoToBitmapConfig(info, &isOpaque);
+    
+    fBitmap.setConfig(config, info.fWidth, info.fHeight, rowBytes);
+    fBitmap.setPixelRef(SkNEW_ARGS(SkDataPixelRef, (data)))->unref();
+    fBitmap.setIsOpaque(isOpaque);
+    fBitmap.setImmutable();
+}
+
+SkImage_Raster::SkImage_Raster(const Info& info, SkPixelRef* pr, size_t rowBytes)
         : INHERITED(info.fWidth, info.fHeight) {
+    SkASSERT(pr->isImmutable());
+
     bool isOpaque;
-    SkBitmap::Config config = InfoToConfig(info, &isOpaque);
+    SkBitmap::Config config = SkImageInfoToBitmapConfig(info, &isOpaque);
 
     fBitmap.setConfig(config, info.fWidth, info.fHeight, rowBytes);
-    fBitmap.setPixelRef(SkNEW_ARGS(SkDataPixelRef, (data)))->unref();
+    fBitmap.setPixelRef(pr);
     fBitmap.setIsOpaque(isOpaque);
-    fBitmap.setImmutable();   // Yea baby!
+    fBitmap.setImmutable();
 }
 
 SkImage_Raster::~SkImage_Raster() {}
diff --git a/src/image/SkImagePriv.cpp b/src/image/SkImagePriv.cpp
new file mode 100644 (file)
index 0000000..d0c56fd
--- /dev/null
@@ -0,0 +1,109 @@
+#include "SkImagePriv.h"
+
+SkBitmap::Config SkImageInfoToBitmapConfig(const SkImage::Info& info,
+                                           bool* isOpaque) {
+    switch (info.fColorType) {
+        case SkImage::kAlpha_8_ColorType:
+            switch (info.fAlphaType) {
+                case SkImage::kIgnore_AlphaType:
+                    // makes no sense
+                    return SkBitmap::kNo_Config;
+
+                case SkImage::kOpaque_AlphaType:
+                    *isOpaque = true;
+                    return SkBitmap::kA8_Config;
+
+                case SkImage::kPremul_AlphaType:
+                case SkImage::kUnpremul_AlphaType:
+                    *isOpaque = false;
+                    return SkBitmap::kA8_Config;
+            }
+            break;
+
+        case SkImage::kRGB_565_ColorType:
+            // we ignore fAlpahType, though some would not make sense
+            *isOpaque = true;
+            return SkBitmap::kRGB_565_Config;
+
+        case SkImage::kRGBA_8888_ColorType:
+        case SkImage::kBGRA_8888_ColorType:
+            // not supported yet
+            return SkBitmap::kNo_Config;
+
+        case SkImage::kPMColor_ColorType:
+            switch (info.fAlphaType) {
+                case SkImage::kIgnore_AlphaType:
+                case SkImage::kUnpremul_AlphaType:
+                    // not supported yet
+                    return SkBitmap::kNo_Config;
+                case SkImage::kOpaque_AlphaType:
+                    *isOpaque = true;
+                    return SkBitmap::kARGB_8888_Config;
+                case SkImage::kPremul_AlphaType:
+                    *isOpaque = false;
+                    return SkBitmap::kARGB_8888_Config;
+            }
+            break;
+    }
+    SkASSERT(!"how did we get here");
+    return SkBitmap::kNo_Config;
+}
+
+int SkImageBytesPerPixel(SkImage::ColorType ct) {
+    static const uint8_t gColorTypeBytesPerPixel[] = {
+        1,  // kAlpha_8_ColorType
+        2,  // kRGB_565_ColorType
+        4,  // kRGBA_8888_ColorType
+        4,  // kBGRA_8888_ColorType
+        4,  // kPMColor_ColorType
+    };
+
+    SkASSERT((size_t)ct < SK_ARRAY_COUNT(gColorTypeBytesPerPixel));
+    return gColorTypeBytesPerPixel[ct];
+}
+
+bool SkBitmapToImageInfo(const SkBitmap& bm, SkImage::Info* info) {
+    switch (bm.config()) {
+        case SkBitmap::kA8_Config:
+            info->fColorType = SkImage::kAlpha_8_ColorType;
+            break;
+
+        case SkBitmap::kRGB_565_Config:
+            info->fColorType = SkImage::kRGB_565_ColorType;
+            break;
+            
+        case SkBitmap::kARGB_8888_Config:
+            info->fColorType = SkImage::kPMColor_ColorType;
+            break;
+        
+        default:
+            return false;
+    }
+    
+    info->fWidth = bm.width();
+    info->fHeight = bm.height();
+    info->fAlphaType = bm.isOpaque() ? SkImage::kOpaque_AlphaType :
+                                       SkImage::kPremul_AlphaType;
+    return true;
+}
+
+SkImage* SkNewImageFromBitmap(const SkBitmap& bm) {
+    SkImage::Info info;
+    if (!SkBitmapToImageInfo(bm, &info)) {
+        return NULL;
+    }
+
+    SkImage* image = NULL;
+    if (bm.isImmutable()) {
+        image = SkNewImageFromPixelRef(info, bm.pixelRef(), bm.rowBytes());
+    } else {
+        bm.lockPixels();
+        if (NULL == bm.getPixels()) {
+            image = SkImage::NewRasterCopy(info, NULL, bm.getPixels(),
+                                           bm.rowBytes());
+        }
+        bm.unlockPixels();
+    }
+    return image;
+}
+
diff --git a/src/image/SkImagePriv.h b/src/image/SkImagePriv.h
new file mode 100644 (file)
index 0000000..1c732c3
--- /dev/null
@@ -0,0 +1,27 @@
+
+#ifndef SkImagePriv_DEFINED
+#define SkImagePriv_DEFINED
+
+#include "SkBitmap.h"
+#include "SkImage.h"
+
+extern SkBitmap::Config SkImageInfoToBitmapConfig(const SkImage::Info&,
+                                                  bool* isOpaque);
+
+extern int SkImageBytesPerPixel(SkImage::ColorType);
+
+extern bool SkBitmapToImageInfo(const SkBitmap&, SkImage::Info*);
+extern SkImage* SkNewImageFromPixelRef(const SkImage::Info&, SkPixelRef*,
+                                       size_t rowBytes);
+
+/**
+ *  Examines the bitmap to decide if it can share the existing pixelRef, or
+ *  if it needs to make a deep-copy of the pixels
+ */
+extern SkImage* SkNewImageFromBitmap(const SkBitmap&);
+
+static inline size_t SkImageMinRowBytes(const SkImage::Info& info) {
+    return info.fWidth * SkImageBytesPerPixel(info.fColorType);
+}
+
+#endif
diff --git a/src/image/SkSurface.cpp b/src/image/SkSurface.cpp
new file mode 100644 (file)
index 0000000..2a9e33a
--- /dev/null
@@ -0,0 +1,173 @@
+#include "SkSurface.h"
+#include "SkImagePriv.h"
+#include "SkCanvas.h"
+
+///////////////////////////////////////////////////////////////////////////////
+
+class SkSurface_Base : public SkSurface {
+public:
+    SkSurface_Base(int width, int height) : INHERITED(width, height) {}
+
+    virtual SkCanvas* onNewCanvas() = 0;
+    virtual SkSurface* onNewSurface(const SkImage::Info&, SkColorSpace*) = 0;
+    virtual SkImage* onNewImageShapshot() = 0;
+    
+    /**
+     *  Default implementation:
+     *
+     *  image = this->newImageSnapshot();
+     *  if (image) {
+     *      image->draw(canvas, ...);
+     *      image->unref();
+     *  }
+     */
+    virtual void onDraw(SkCanvas*, SkScalar x, SkScalar y, const SkPaint*);
+
+private:
+    typedef SkSurface INHERITED;
+};
+
+void SkSurface_Base::onDraw(SkCanvas* canvas, SkScalar x, SkScalar y,
+                            const SkPaint* paint) {
+    SkImage* image = this->newImageShapshot();
+    if (image) {
+        image->draw(canvas, x, y, paint);
+        image->unref();
+    }
+}
+
+static SkSurface_Base* asSB(SkSurface* surface) {
+    return static_cast<SkSurface_Base*>(surface);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+SkSurface::SkSurface(int width, int height) : fWidth(width), fHeight(height) {
+    SkASSERT(width >= 0);
+    SkASSERT(height >= 0);
+    fGenerationID = 0;
+}
+
+SkCanvas* SkSurface::newCanvas() {
+    return asSB(this)->onNewCanvas();
+}
+
+SkSurface* SkSurface::newSurface(const SkImage::Info& info, SkColorSpace* cs) {
+    return asSB(this)->onNewSurface(info, cs);
+}
+
+SkImage* SkSurface::newImageShapshot() {
+    return asSB(this)->onNewImageShapshot();
+}
+
+void SkSurface::draw(SkCanvas* canvas, SkScalar x, SkScalar y,
+                     const SkPaint* paint) {
+    return asSB(this)->onDraw(canvas, x, y, paint);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+class SkSurface_Raster : public SkSurface {
+public:
+    static bool Valid(const SkImage::Info&, SkColorSpace*, size_t rb);
+
+    SkSurface_Raster(const SkImage::Info&, SkColorSpace*, void*, size_t rb);
+
+    virtual SkCanvas* onNewCanvas() SK_OVERRIDE;
+    virtual SkSurface* onNewSurface(const SkImage::Info&, SkColorSpace*) SK_OVERRIDE;
+    virtual SkImage* onNewImageShapshot() SK_OVERRIDE;
+    virtual void onDraw(SkCanvas*, SkScalar x, SkScalar y,
+                        const SkPaint*) SK_OVERRIDE;
+
+private:
+    SkBitmap    fBitmap;
+
+    typedef SkSurface INHERITED;
+};
+
+bool SkSurface_Raster::Valid(const SkImage::Info& info, SkColorSpace* cs,
+                             size_t rowBytes) {
+    static size_t kMaxTotalSize = (1 << 31) - 1;
+
+    bool isOpaque;
+    SkBitmap::Config config = SkImageInfoToBitmapConfig(info, &isOpaque);
+
+    int shift = 0;
+    switch (config) {
+        case SkBitmap::kA8_Config:
+            shift = 0;
+            break;
+        case SkBitmap::kRGB_565_Config:
+            shift = 1;
+            break;
+        case SkBitmap::kARGB_8888_Config:
+            shift = 2;
+            break;
+        default:
+            return false;
+    }
+
+    // TODO: examine colorspace
+
+    uint64_t minRB = (uint64_t)info.fWidth << shift;
+    if (minRB > rowBytes) {
+        return false;
+    }
+
+    size_t alignedRowBytes = rowBytes >> shift << shift;
+    if (alignedRowBytes != rowBytes) {
+        return false;
+    }
+
+    uint64_t size = (uint64_t)info.fHeight * rowBytes;
+    if (size > kMaxTotalSize) {
+        return false;
+    }
+    
+    return true;
+}
+
+SkSurface_Raster::SkSurface_Raster(const SkImage::Info& info, SkColorSpace* cs,
+                                   void* pixels, size_t rb)
+        : INHERITED(info.fWidth, info.fHeight) {
+    bool isOpaque;
+    SkBitmap::Config config = SkImageInfoToBitmapConfig(info, &isOpaque);
+
+    fBitmap.setConfig(config, info.fWidth, info.fHeight, rb);
+    fBitmap.setPixels(pixels);
+    fBitmap.setIsOpaque(isOpaque);
+}
+
+SkCanvas* SkSurface_Raster::onNewCanvas() {
+    return SkNEW_ARGS(SkCanvas, (fBitmap));
+}
+
+SkSurface* SkSurface_Raster::onNewSurface(const SkImage::Info& info,
+                                          SkColorSpace* cs) {
+    return SkSurface::NewRaster(info, cs);
+}
+
+SkImage* SkSurface_Raster::onNewImageShapshot() {
+    return SkNewImageFromBitmap(fBitmap);
+}
+
+void SkSurface_Raster::onDraw(SkCanvas* canvas, SkScalar x, SkScalar y,
+                              const SkPaint* paint) {
+    canvas->drawBitmap(fBitmap, x, y, paint);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+SkSurface* SkSurface::NewRasterDirect(const SkImage::Info& info,
+                                      SkColorSpace* cs,
+                                      void* pixels, size_t rowBytes) {
+    if (!SkSurface_Raster::Valid(info, cs, rowBytes)) {
+        return NULL;
+    }
+    if (NULL == pixels) {
+        return NULL;
+    }
+    
+    return SkNEW_ARGS(SkSurface_Raster, (info, cs, pixels, rowBytes));
+}
+