Tue, 06 Jan 2015 21:39:09 +0100
Conditionally force memory storage according to privacy.thirdparty.isolate;
This solves Tor bug #9701, complying with disk avoidance documented in
https://www.torproject.org/projects/torbrowser/design/#disk-avoidance.
michael@0 | 1 | /* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*- |
michael@0 | 2 | * This Source Code Form is subject to the terms of the Mozilla Public |
michael@0 | 3 | * License, v. 2.0. If a copy of the MPL was not distributed with this |
michael@0 | 4 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
michael@0 | 5 | |
michael@0 | 6 | |
michael@0 | 7 | #include "mozilla/MemoryReporting.h" |
michael@0 | 8 | #if defined(HAVE_POSIX_MEMALIGN) |
michael@0 | 9 | #include "gfxAlphaRecovery.h" |
michael@0 | 10 | #endif |
michael@0 | 11 | #include "gfxImageSurface.h" |
michael@0 | 12 | |
michael@0 | 13 | #include "cairo.h" |
michael@0 | 14 | #include "mozilla/gfx/2D.h" |
michael@0 | 15 | #include "gfx2DGlue.h" |
michael@0 | 16 | #include <algorithm> |
michael@0 | 17 | |
michael@0 | 18 | using namespace mozilla; |
michael@0 | 19 | using namespace mozilla::gfx; |
michael@0 | 20 | |
michael@0 | 21 | gfxImageSurface::gfxImageSurface() |
michael@0 | 22 | : mSize(0, 0), |
michael@0 | 23 | mOwnsData(false), |
michael@0 | 24 | mFormat(gfxImageFormat::Unknown), |
michael@0 | 25 | mStride(0) |
michael@0 | 26 | { |
michael@0 | 27 | } |
michael@0 | 28 | |
michael@0 | 29 | void |
michael@0 | 30 | gfxImageSurface::InitFromSurface(cairo_surface_t *csurf) |
michael@0 | 31 | { |
michael@0 | 32 | mSize.width = cairo_image_surface_get_width(csurf); |
michael@0 | 33 | mSize.height = cairo_image_surface_get_height(csurf); |
michael@0 | 34 | mData = cairo_image_surface_get_data(csurf); |
michael@0 | 35 | mFormat = (gfxImageFormat) cairo_image_surface_get_format(csurf); |
michael@0 | 36 | mOwnsData = false; |
michael@0 | 37 | mStride = cairo_image_surface_get_stride(csurf); |
michael@0 | 38 | |
michael@0 | 39 | Init(csurf, true); |
michael@0 | 40 | } |
michael@0 | 41 | |
michael@0 | 42 | gfxImageSurface::gfxImageSurface(unsigned char *aData, const gfxIntSize& aSize, |
michael@0 | 43 | long aStride, gfxImageFormat aFormat) |
michael@0 | 44 | { |
michael@0 | 45 | InitWithData(aData, aSize, aStride, aFormat); |
michael@0 | 46 | } |
michael@0 | 47 | |
michael@0 | 48 | void |
michael@0 | 49 | gfxImageSurface::MakeInvalid() |
michael@0 | 50 | { |
michael@0 | 51 | mSize = gfxIntSize(-1, -1); |
michael@0 | 52 | mData = nullptr; |
michael@0 | 53 | mStride = 0; |
michael@0 | 54 | } |
michael@0 | 55 | |
michael@0 | 56 | void |
michael@0 | 57 | gfxImageSurface::InitWithData(unsigned char *aData, const gfxIntSize& aSize, |
michael@0 | 58 | long aStride, gfxImageFormat aFormat) |
michael@0 | 59 | { |
michael@0 | 60 | mSize = aSize; |
michael@0 | 61 | mOwnsData = false; |
michael@0 | 62 | mData = aData; |
michael@0 | 63 | mFormat = aFormat; |
michael@0 | 64 | mStride = aStride; |
michael@0 | 65 | |
michael@0 | 66 | if (!CheckSurfaceSize(aSize)) |
michael@0 | 67 | MakeInvalid(); |
michael@0 | 68 | |
michael@0 | 69 | cairo_surface_t *surface = |
michael@0 | 70 | cairo_image_surface_create_for_data((unsigned char*)mData, |
michael@0 | 71 | (cairo_format_t)(int)mFormat, |
michael@0 | 72 | mSize.width, |
michael@0 | 73 | mSize.height, |
michael@0 | 74 | mStride); |
michael@0 | 75 | |
michael@0 | 76 | // cairo_image_surface_create_for_data can return a 'null' surface |
michael@0 | 77 | // in out of memory conditions. The gfxASurface::Init call checks |
michael@0 | 78 | // the surface it receives to see if there is an error with the |
michael@0 | 79 | // surface and handles it appropriately. That is why there is |
michael@0 | 80 | // no check here. |
michael@0 | 81 | Init(surface); |
michael@0 | 82 | } |
michael@0 | 83 | |
michael@0 | 84 | static void* |
michael@0 | 85 | TryAllocAlignedBytes(size_t aSize) |
michael@0 | 86 | { |
michael@0 | 87 | // Use fallible allocators here |
michael@0 | 88 | #if defined(HAVE_POSIX_MEMALIGN) |
michael@0 | 89 | void* ptr; |
michael@0 | 90 | // Try to align for fast alpha recovery. This should only help |
michael@0 | 91 | // cairo too, can't hurt. |
michael@0 | 92 | return moz_posix_memalign(&ptr, |
michael@0 | 93 | 1 << gfxAlphaRecovery::GoodAlignmentLog2(), |
michael@0 | 94 | aSize) ? |
michael@0 | 95 | nullptr : ptr; |
michael@0 | 96 | #else |
michael@0 | 97 | // Oh well, hope that luck is with us in the allocator |
michael@0 | 98 | return moz_malloc(aSize); |
michael@0 | 99 | #endif |
michael@0 | 100 | } |
michael@0 | 101 | |
michael@0 | 102 | gfxImageSurface::gfxImageSurface(const gfxIntSize& size, gfxImageFormat format, bool aClear) |
michael@0 | 103 | : mSize(size), mData(nullptr), mFormat(format) |
michael@0 | 104 | { |
michael@0 | 105 | AllocateAndInit(0, 0, aClear); |
michael@0 | 106 | } |
michael@0 | 107 | |
michael@0 | 108 | void |
michael@0 | 109 | gfxImageSurface::AllocateAndInit(long aStride, int32_t aMinimalAllocation, |
michael@0 | 110 | bool aClear) |
michael@0 | 111 | { |
michael@0 | 112 | // The callers should set mSize and mFormat. |
michael@0 | 113 | MOZ_ASSERT(!mData); |
michael@0 | 114 | mData = nullptr; |
michael@0 | 115 | mOwnsData = false; |
michael@0 | 116 | |
michael@0 | 117 | mStride = aStride > 0 ? aStride : ComputeStride(); |
michael@0 | 118 | if (aMinimalAllocation < mSize.height * mStride) |
michael@0 | 119 | aMinimalAllocation = mSize.height * mStride; |
michael@0 | 120 | |
michael@0 | 121 | if (!CheckSurfaceSize(mSize)) |
michael@0 | 122 | MakeInvalid(); |
michael@0 | 123 | |
michael@0 | 124 | // if we have a zero-sized surface, just leave mData nullptr |
michael@0 | 125 | if (mSize.height * mStride > 0) { |
michael@0 | 126 | |
michael@0 | 127 | // This can fail to allocate memory aligned as we requested, |
michael@0 | 128 | // or it can fail to allocate any memory at all. |
michael@0 | 129 | mData = (unsigned char *) TryAllocAlignedBytes(aMinimalAllocation); |
michael@0 | 130 | if (!mData) |
michael@0 | 131 | return; |
michael@0 | 132 | if (aClear) |
michael@0 | 133 | memset(mData, 0, aMinimalAllocation); |
michael@0 | 134 | } |
michael@0 | 135 | |
michael@0 | 136 | mOwnsData = true; |
michael@0 | 137 | |
michael@0 | 138 | cairo_surface_t *surface = |
michael@0 | 139 | cairo_image_surface_create_for_data((unsigned char*)mData, |
michael@0 | 140 | (cairo_format_t)(int)mFormat, |
michael@0 | 141 | mSize.width, |
michael@0 | 142 | mSize.height, |
michael@0 | 143 | mStride); |
michael@0 | 144 | |
michael@0 | 145 | Init(surface); |
michael@0 | 146 | |
michael@0 | 147 | if (mSurfaceValid) { |
michael@0 | 148 | RecordMemoryUsed(mSize.height * ComputeStride() + |
michael@0 | 149 | sizeof(gfxImageSurface)); |
michael@0 | 150 | } |
michael@0 | 151 | } |
michael@0 | 152 | |
michael@0 | 153 | gfxImageSurface::gfxImageSurface(const gfxIntSize& size, gfxImageFormat format, |
michael@0 | 154 | long aStride, int32_t aExtraBytes, bool aClear) |
michael@0 | 155 | : mSize(size), mData(nullptr), mFormat(format) |
michael@0 | 156 | { |
michael@0 | 157 | AllocateAndInit(aStride, aExtraBytes, aClear); |
michael@0 | 158 | } |
michael@0 | 159 | |
michael@0 | 160 | gfxImageSurface::gfxImageSurface(cairo_surface_t *csurf) |
michael@0 | 161 | { |
michael@0 | 162 | mSize.width = cairo_image_surface_get_width(csurf); |
michael@0 | 163 | mSize.height = cairo_image_surface_get_height(csurf); |
michael@0 | 164 | mData = cairo_image_surface_get_data(csurf); |
michael@0 | 165 | mFormat = (gfxImageFormat) cairo_image_surface_get_format(csurf); |
michael@0 | 166 | mOwnsData = false; |
michael@0 | 167 | mStride = cairo_image_surface_get_stride(csurf); |
michael@0 | 168 | |
michael@0 | 169 | Init(csurf, true); |
michael@0 | 170 | } |
michael@0 | 171 | |
michael@0 | 172 | gfxImageSurface::~gfxImageSurface() |
michael@0 | 173 | { |
michael@0 | 174 | if (mOwnsData) |
michael@0 | 175 | free(mData); |
michael@0 | 176 | } |
michael@0 | 177 | |
michael@0 | 178 | /*static*/ long |
michael@0 | 179 | gfxImageSurface::ComputeStride(const gfxIntSize& aSize, gfxImageFormat aFormat) |
michael@0 | 180 | { |
michael@0 | 181 | long stride; |
michael@0 | 182 | |
michael@0 | 183 | if (aFormat == gfxImageFormat::ARGB32) |
michael@0 | 184 | stride = aSize.width * 4; |
michael@0 | 185 | else if (aFormat == gfxImageFormat::RGB24) |
michael@0 | 186 | stride = aSize.width * 4; |
michael@0 | 187 | else if (aFormat == gfxImageFormat::RGB16_565) |
michael@0 | 188 | stride = aSize.width * 2; |
michael@0 | 189 | else if (aFormat == gfxImageFormat::A8) |
michael@0 | 190 | stride = aSize.width; |
michael@0 | 191 | else if (aFormat == gfxImageFormat::A1) { |
michael@0 | 192 | stride = (aSize.width + 7) / 8; |
michael@0 | 193 | } else { |
michael@0 | 194 | NS_WARNING("Unknown format specified to gfxImageSurface!"); |
michael@0 | 195 | stride = aSize.width * 4; |
michael@0 | 196 | } |
michael@0 | 197 | |
michael@0 | 198 | stride = ((stride + 3) / 4) * 4; |
michael@0 | 199 | |
michael@0 | 200 | return stride; |
michael@0 | 201 | } |
michael@0 | 202 | |
michael@0 | 203 | size_t |
michael@0 | 204 | gfxImageSurface::SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const |
michael@0 | 205 | { |
michael@0 | 206 | size_t n = gfxASurface::SizeOfExcludingThis(aMallocSizeOf); |
michael@0 | 207 | if (mOwnsData) { |
michael@0 | 208 | n += aMallocSizeOf(mData); |
michael@0 | 209 | } |
michael@0 | 210 | return n; |
michael@0 | 211 | } |
michael@0 | 212 | |
michael@0 | 213 | size_t |
michael@0 | 214 | gfxImageSurface::SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const |
michael@0 | 215 | { |
michael@0 | 216 | return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf); |
michael@0 | 217 | } |
michael@0 | 218 | |
michael@0 | 219 | bool |
michael@0 | 220 | gfxImageSurface::SizeOfIsMeasured() const |
michael@0 | 221 | { |
michael@0 | 222 | return true; |
michael@0 | 223 | } |
michael@0 | 224 | |
michael@0 | 225 | // helper function for the CopyFrom methods |
michael@0 | 226 | static void |
michael@0 | 227 | CopyForStride(unsigned char* aDest, unsigned char* aSrc, const gfxIntSize& aSize, long aDestStride, long aSrcStride) |
michael@0 | 228 | { |
michael@0 | 229 | if (aDestStride == aSrcStride) { |
michael@0 | 230 | memcpy (aDest, aSrc, aSrcStride * aSize.height); |
michael@0 | 231 | } else { |
michael@0 | 232 | int lineSize = std::min(aDestStride, aSrcStride); |
michael@0 | 233 | for (int i = 0; i < aSize.height; i++) { |
michael@0 | 234 | unsigned char* src = aSrc + aSrcStride * i; |
michael@0 | 235 | unsigned char* dst = aDest + aDestStride * i; |
michael@0 | 236 | |
michael@0 | 237 | memcpy (dst, src, lineSize); |
michael@0 | 238 | } |
michael@0 | 239 | } |
michael@0 | 240 | } |
michael@0 | 241 | |
michael@0 | 242 | // helper function for the CopyFrom methods |
michael@0 | 243 | static bool |
michael@0 | 244 | FormatsAreCompatible(gfxImageFormat a1, gfxImageFormat a2) |
michael@0 | 245 | { |
michael@0 | 246 | if (a1 != a2 && |
michael@0 | 247 | !(a1 == gfxImageFormat::ARGB32 && |
michael@0 | 248 | a2 == gfxImageFormat::RGB24) && |
michael@0 | 249 | !(a1 == gfxImageFormat::RGB24 && |
michael@0 | 250 | a2 == gfxImageFormat::ARGB32)) { |
michael@0 | 251 | return false; |
michael@0 | 252 | } |
michael@0 | 253 | |
michael@0 | 254 | return true; |
michael@0 | 255 | } |
michael@0 | 256 | |
michael@0 | 257 | bool |
michael@0 | 258 | gfxImageSurface::CopyFrom (SourceSurface *aSurface) |
michael@0 | 259 | { |
michael@0 | 260 | mozilla::RefPtr<DataSourceSurface> data = aSurface->GetDataSurface(); |
michael@0 | 261 | |
michael@0 | 262 | if (!data) { |
michael@0 | 263 | return false; |
michael@0 | 264 | } |
michael@0 | 265 | |
michael@0 | 266 | gfxIntSize size(data->GetSize().width, data->GetSize().height); |
michael@0 | 267 | if (size != mSize) { |
michael@0 | 268 | return false; |
michael@0 | 269 | } |
michael@0 | 270 | |
michael@0 | 271 | if (!FormatsAreCompatible(SurfaceFormatToImageFormat(aSurface->GetFormat()), |
michael@0 | 272 | mFormat)) { |
michael@0 | 273 | return false; |
michael@0 | 274 | } |
michael@0 | 275 | |
michael@0 | 276 | CopyForStride(mData, data->GetData(), size, mStride, data->Stride()); |
michael@0 | 277 | |
michael@0 | 278 | return true; |
michael@0 | 279 | } |
michael@0 | 280 | |
michael@0 | 281 | |
michael@0 | 282 | bool |
michael@0 | 283 | gfxImageSurface::CopyFrom(gfxImageSurface *other) |
michael@0 | 284 | { |
michael@0 | 285 | if (other->mSize != mSize) { |
michael@0 | 286 | return false; |
michael@0 | 287 | } |
michael@0 | 288 | |
michael@0 | 289 | if (!FormatsAreCompatible(other->mFormat, mFormat)) { |
michael@0 | 290 | return false; |
michael@0 | 291 | } |
michael@0 | 292 | |
michael@0 | 293 | CopyForStride(mData, other->mData, mSize, mStride, other->mStride); |
michael@0 | 294 | |
michael@0 | 295 | return true; |
michael@0 | 296 | } |
michael@0 | 297 | |
michael@0 | 298 | bool |
michael@0 | 299 | gfxImageSurface::CopyTo(SourceSurface *aSurface) { |
michael@0 | 300 | mozilla::RefPtr<DataSourceSurface> data = aSurface->GetDataSurface(); |
michael@0 | 301 | |
michael@0 | 302 | if (!data) { |
michael@0 | 303 | return false; |
michael@0 | 304 | } |
michael@0 | 305 | |
michael@0 | 306 | gfxIntSize size(data->GetSize().width, data->GetSize().height); |
michael@0 | 307 | if (size != mSize) { |
michael@0 | 308 | return false; |
michael@0 | 309 | } |
michael@0 | 310 | |
michael@0 | 311 | if (!FormatsAreCompatible(SurfaceFormatToImageFormat(aSurface->GetFormat()), |
michael@0 | 312 | mFormat)) { |
michael@0 | 313 | return false; |
michael@0 | 314 | } |
michael@0 | 315 | |
michael@0 | 316 | CopyForStride(data->GetData(), mData, size, data->Stride(), mStride); |
michael@0 | 317 | |
michael@0 | 318 | return true; |
michael@0 | 319 | } |
michael@0 | 320 | |
michael@0 | 321 | TemporaryRef<DataSourceSurface> |
michael@0 | 322 | gfxImageSurface::CopyToB8G8R8A8DataSourceSurface() |
michael@0 | 323 | { |
michael@0 | 324 | RefPtr<DataSourceSurface> dataSurface = |
michael@0 | 325 | Factory::CreateDataSourceSurface(IntSize(GetSize().width, GetSize().height), |
michael@0 | 326 | SurfaceFormat::B8G8R8A8); |
michael@0 | 327 | if (dataSurface) { |
michael@0 | 328 | CopyTo(dataSurface); |
michael@0 | 329 | } |
michael@0 | 330 | return dataSurface.forget(); |
michael@0 | 331 | } |
michael@0 | 332 | |
michael@0 | 333 | already_AddRefed<gfxSubimageSurface> |
michael@0 | 334 | gfxImageSurface::GetSubimage(const gfxRect& aRect) |
michael@0 | 335 | { |
michael@0 | 336 | gfxRect r(aRect); |
michael@0 | 337 | r.Round(); |
michael@0 | 338 | MOZ_ASSERT(gfxRect(0, 0, mSize.width, mSize.height).Contains(r)); |
michael@0 | 339 | |
michael@0 | 340 | gfxImageFormat format = Format(); |
michael@0 | 341 | |
michael@0 | 342 | unsigned char* subData = Data() + |
michael@0 | 343 | (Stride() * (int)r.Y()) + |
michael@0 | 344 | (int)r.X() * gfxASurface::BytePerPixelFromFormat(Format()); |
michael@0 | 345 | |
michael@0 | 346 | if (format == gfxImageFormat::ARGB32 && |
michael@0 | 347 | GetOpaqueRect().Contains(aRect)) { |
michael@0 | 348 | format = gfxImageFormat::RGB24; |
michael@0 | 349 | } |
michael@0 | 350 | |
michael@0 | 351 | nsRefPtr<gfxSubimageSurface> image = |
michael@0 | 352 | new gfxSubimageSurface(this, subData, |
michael@0 | 353 | gfxIntSize((int)r.Width(), (int)r.Height()), |
michael@0 | 354 | format); |
michael@0 | 355 | |
michael@0 | 356 | return image.forget(); |
michael@0 | 357 | } |
michael@0 | 358 | |
michael@0 | 359 | gfxSubimageSurface::gfxSubimageSurface(gfxImageSurface* aParent, |
michael@0 | 360 | unsigned char* aData, |
michael@0 | 361 | const gfxIntSize& aSize, |
michael@0 | 362 | gfxImageFormat aFormat) |
michael@0 | 363 | : gfxImageSurface(aData, aSize, aParent->Stride(), aFormat) |
michael@0 | 364 | , mParent(aParent) |
michael@0 | 365 | { |
michael@0 | 366 | } |
michael@0 | 367 | |
michael@0 | 368 | already_AddRefed<gfxImageSurface> |
michael@0 | 369 | gfxImageSurface::GetAsImageSurface() |
michael@0 | 370 | { |
michael@0 | 371 | nsRefPtr<gfxImageSurface> surface = this; |
michael@0 | 372 | return surface.forget(); |
michael@0 | 373 | } |
michael@0 | 374 | |
michael@0 | 375 | void |
michael@0 | 376 | gfxImageSurface::MovePixels(const nsIntRect& aSourceRect, |
michael@0 | 377 | const nsIntPoint& aDestTopLeft) |
michael@0 | 378 | { |
michael@0 | 379 | const nsIntRect bounds(0, 0, mSize.width, mSize.height); |
michael@0 | 380 | nsIntPoint offset = aDestTopLeft - aSourceRect.TopLeft(); |
michael@0 | 381 | nsIntRect clippedSource = aSourceRect; |
michael@0 | 382 | clippedSource.IntersectRect(clippedSource, bounds); |
michael@0 | 383 | nsIntRect clippedDest = clippedSource + offset; |
michael@0 | 384 | clippedDest.IntersectRect(clippedDest, bounds); |
michael@0 | 385 | const nsIntRect dest = clippedDest; |
michael@0 | 386 | const nsIntRect source = dest - offset; |
michael@0 | 387 | // NB: this relies on IntersectRect() and operator+/- preserving |
michael@0 | 388 | // x/y for empty rectangles |
michael@0 | 389 | NS_ABORT_IF_FALSE(bounds.Contains(dest) && bounds.Contains(source) && |
michael@0 | 390 | aSourceRect.Contains(source) && |
michael@0 | 391 | nsIntRect(aDestTopLeft, aSourceRect.Size()).Contains(dest) && |
michael@0 | 392 | source.Size() == dest.Size() && |
michael@0 | 393 | offset == (dest.TopLeft() - source.TopLeft()), |
michael@0 | 394 | "Messed up clipping, crash or corruption will follow"); |
michael@0 | 395 | if (source.IsEmpty() || source.IsEqualInterior(dest)) { |
michael@0 | 396 | return; |
michael@0 | 397 | } |
michael@0 | 398 | |
michael@0 | 399 | long naturalStride = ComputeStride(mSize, mFormat); |
michael@0 | 400 | if (mStride == naturalStride && dest.width == bounds.width) { |
michael@0 | 401 | // Fast path: this is a vertical shift of some rows in a |
michael@0 | 402 | // "normal" image surface. We can directly memmove and |
michael@0 | 403 | // hopefully stay in SIMD land. |
michael@0 | 404 | unsigned char* dst = mData + dest.y * mStride; |
michael@0 | 405 | const unsigned char* src = mData + source.y * mStride; |
michael@0 | 406 | size_t nBytes = dest.height * mStride; |
michael@0 | 407 | memmove(dst, src, nBytes); |
michael@0 | 408 | return; |
michael@0 | 409 | } |
michael@0 | 410 | |
michael@0 | 411 | // Slow(er) path: have to move row-by-row. |
michael@0 | 412 | const int32_t bpp = BytePerPixelFromFormat(mFormat); |
michael@0 | 413 | const size_t nRowBytes = dest.width * bpp; |
michael@0 | 414 | // dstRow points at the first pixel within the current destination |
michael@0 | 415 | // row, and similarly for srcRow. endSrcRow is one row beyond the |
michael@0 | 416 | // last row we need to copy. stride is either +mStride or |
michael@0 | 417 | // -mStride, depending on which direction we're copying. |
michael@0 | 418 | unsigned char* dstRow; |
michael@0 | 419 | unsigned char* srcRow; |
michael@0 | 420 | unsigned char* endSrcRow; // NB: this may point outside the image |
michael@0 | 421 | long stride; |
michael@0 | 422 | if (dest.y > source.y) { |
michael@0 | 423 | // We're copying down from source to dest, so walk backwards |
michael@0 | 424 | // starting from the last rows to avoid stomping pixels we |
michael@0 | 425 | // need. |
michael@0 | 426 | stride = -mStride; |
michael@0 | 427 | dstRow = mData + dest.x * bpp + (dest.YMost() - 1) * mStride; |
michael@0 | 428 | srcRow = mData + source.x * bpp + (source.YMost() - 1) * mStride; |
michael@0 | 429 | endSrcRow = mData + source.x * bpp + (source.y - 1) * mStride; |
michael@0 | 430 | } else { |
michael@0 | 431 | stride = mStride; |
michael@0 | 432 | dstRow = mData + dest.x * bpp + dest.y * mStride; |
michael@0 | 433 | srcRow = mData + source.x * bpp + source.y * mStride; |
michael@0 | 434 | endSrcRow = mData + source.x * bpp + source.YMost() * mStride; |
michael@0 | 435 | } |
michael@0 | 436 | |
michael@0 | 437 | for (; srcRow != endSrcRow; dstRow += stride, srcRow += stride) { |
michael@0 | 438 | memmove(dstRow, srcRow, nRowBytes); |
michael@0 | 439 | } |
michael@0 | 440 | } |