| OLD | NEW |
| 1 | 1 |
| 2 /* | 2 /* |
| 3 * Copyright 2008 The Android Open Source Project | 3 * Copyright 2008 The Android Open Source Project |
| 4 * | 4 * |
| 5 * Use of this source code is governed by a BSD-style license that can be | 5 * Use of this source code is governed by a BSD-style license that can be |
| 6 * found in the LICENSE file. | 6 * found in the LICENSE file. |
| 7 */ | 7 */ |
| 8 | 8 |
| 9 | 9 |
| 10 #include "SkBitmap.h" | 10 #include "SkBitmap.h" |
| (...skipping 11 matching lines...) Expand all Loading... |
| 22 #include "SkUtils.h" | 22 #include "SkUtils.h" |
| 23 #include "SkValidationUtils.h" | 23 #include "SkValidationUtils.h" |
| 24 #include "SkPackBits.h" | 24 #include "SkPackBits.h" |
| 25 #include <new> | 25 #include <new> |
| 26 | 26 |
| 27 static bool reset_return_false(SkBitmap* bm) { | 27 static bool reset_return_false(SkBitmap* bm) { |
| 28 bm->reset(); | 28 bm->reset(); |
| 29 return false; | 29 return false; |
| 30 } | 30 } |
| 31 | 31 |
| 32 struct MipLevel { | |
| 33 void* fPixels; | |
| 34 uint32_t fRowBytes; | |
| 35 uint32_t fWidth, fHeight; | |
| 36 }; | |
| 37 | |
| 38 struct SkBitmap::MipMap : SkNoncopyable { | |
| 39 int32_t fRefCnt; | |
| 40 int fLevelCount; | |
| 41 // MipLevel fLevel[fLevelCount]; | |
| 42 // Pixels[] | |
| 43 | |
| 44 static MipMap* Alloc(int levelCount, size_t pixelSize) { | |
| 45 if (levelCount < 0) { | |
| 46 return NULL; | |
| 47 } | |
| 48 int64_t size = (levelCount + 1) * sizeof(MipLevel); | |
| 49 size += sizeof(MipMap) + pixelSize; | |
| 50 if (!sk_64_isS32(size)) { | |
| 51 return NULL; | |
| 52 } | |
| 53 MipMap* mm = (MipMap*)sk_malloc_throw(sk_64_asS32(size)); | |
| 54 mm->fRefCnt = 1; | |
| 55 mm->fLevelCount = levelCount; | |
| 56 return mm; | |
| 57 } | |
| 58 | |
| 59 const MipLevel* levels() const { return (const MipLevel*)(this + 1); } | |
| 60 MipLevel* levels() { return (MipLevel*)(this + 1); } | |
| 61 | |
| 62 const void* pixels() const { return levels() + fLevelCount; } | |
| 63 void* pixels() { return levels() + fLevelCount; } | |
| 64 | |
| 65 void ref() { | |
| 66 if (SK_MaxS32 == sk_atomic_inc(&fRefCnt)) { | |
| 67 sk_throw(); | |
| 68 } | |
| 69 } | |
| 70 void unref() { | |
| 71 SkASSERT(fRefCnt > 0); | |
| 72 if (sk_atomic_dec(&fRefCnt) == 1) { | |
| 73 sk_free(this); | |
| 74 } | |
| 75 } | |
| 76 }; | |
| 77 | |
| 78 /////////////////////////////////////////////////////////////////////////////// | |
| 79 /////////////////////////////////////////////////////////////////////////////// | |
| 80 | |
| 81 SkBitmap::SkBitmap() { | 32 SkBitmap::SkBitmap() { |
| 82 sk_bzero(this, sizeof(*this)); | 33 sk_bzero(this, sizeof(*this)); |
| 83 } | 34 } |
| 84 | 35 |
| 85 SkBitmap::SkBitmap(const SkBitmap& src) { | 36 SkBitmap::SkBitmap(const SkBitmap& src) { |
| 86 SkDEBUGCODE(src.validate();) | 37 SkDEBUGCODE(src.validate();) |
| 87 sk_bzero(this, sizeof(*this)); | 38 sk_bzero(this, sizeof(*this)); |
| 88 *this = src; | 39 *this = src; |
| 89 SkDEBUGCODE(this->validate();) | 40 SkDEBUGCODE(this->validate();) |
| 90 } | 41 } |
| 91 | 42 |
| 92 SkBitmap::~SkBitmap() { | 43 SkBitmap::~SkBitmap() { |
| 93 SkDEBUGCODE(this->validate();) | 44 SkDEBUGCODE(this->validate();) |
| 94 this->freePixels(); | 45 this->freePixels(); |
| 95 } | 46 } |
| 96 | 47 |
| 97 SkBitmap& SkBitmap::operator=(const SkBitmap& src) { | 48 SkBitmap& SkBitmap::operator=(const SkBitmap& src) { |
| 98 if (this != &src) { | 49 if (this != &src) { |
| 99 this->freePixels(); | 50 this->freePixels(); |
| 100 memcpy(this, &src, sizeof(src)); | 51 memcpy(this, &src, sizeof(src)); |
| 101 | 52 |
| 102 // inc src reference counts | 53 // inc src reference counts |
| 103 SkSafeRef(src.fPixelRef); | 54 SkSafeRef(src.fPixelRef); |
| 104 SkSafeRef(src.fMipMap); | |
| 105 | 55 |
| 106 // we reset our locks if we get blown away | 56 // we reset our locks if we get blown away |
| 107 fPixelLockCount = 0; | 57 fPixelLockCount = 0; |
| 108 | 58 |
| 109 if (fPixelRef) { | 59 if (fPixelRef) { |
| 110 // ignore the values from the memcpy | 60 // ignore the values from the memcpy |
| 111 fPixels = NULL; | 61 fPixels = NULL; |
| 112 fColorTable = NULL; | 62 fColorTable = NULL; |
| 113 // Note that what to for genID is somewhat arbitrary. We have no | 63 // Note that what to for genID is somewhat arbitrary. We have no |
| 114 // way to track changes to raw pixels across multiple SkBitmaps. | 64 // way to track changes to raw pixels across multiple SkBitmaps. |
| 115 // Would benefit from an SkRawPixelRef type created by | 65 // Would benefit from an SkRawPixelRef type created by |
| 116 // setPixels. | 66 // setPixels. |
| 117 // Just leave the memcpy'ed one but they'll get out of sync | 67 // Just leave the memcpy'ed one but they'll get out of sync |
| 118 // as soon either is modified. | 68 // as soon either is modified. |
| 119 } | 69 } |
| 120 } | 70 } |
| 121 | 71 |
| 122 SkDEBUGCODE(this->validate();) | 72 SkDEBUGCODE(this->validate();) |
| 123 return *this; | 73 return *this; |
| 124 } | 74 } |
| 125 | 75 |
| 126 void SkBitmap::swap(SkBitmap& other) { | 76 void SkBitmap::swap(SkBitmap& other) { |
| 127 SkTSwap(fColorTable, other.fColorTable); | 77 SkTSwap(fColorTable, other.fColorTable); |
| 128 SkTSwap(fPixelRef, other.fPixelRef); | 78 SkTSwap(fPixelRef, other.fPixelRef); |
| 129 SkTSwap(fPixelRefOrigin, other.fPixelRefOrigin); | 79 SkTSwap(fPixelRefOrigin, other.fPixelRefOrigin); |
| 130 SkTSwap(fPixelLockCount, other.fPixelLockCount); | 80 SkTSwap(fPixelLockCount, other.fPixelLockCount); |
| 131 SkTSwap(fMipMap, other.fMipMap); | |
| 132 SkTSwap(fPixels, other.fPixels); | 81 SkTSwap(fPixels, other.fPixels); |
| 133 SkTSwap(fInfo, other.fInfo); | 82 SkTSwap(fInfo, other.fInfo); |
| 134 SkTSwap(fRowBytes, other.fRowBytes); | 83 SkTSwap(fRowBytes, other.fRowBytes); |
| 135 SkTSwap(fFlags, other.fFlags); | 84 SkTSwap(fFlags, other.fFlags); |
| 136 | 85 |
| 137 SkDEBUGCODE(this->validate();) | 86 SkDEBUGCODE(this->validate();) |
| 138 } | 87 } |
| 139 | 88 |
| 140 void SkBitmap::reset() { | 89 void SkBitmap::reset() { |
| 141 this->freePixels(); | 90 this->freePixels(); |
| (...skipping 393 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 535 return false; | 484 return false; |
| 536 } | 485 } |
| 537 | 486 |
| 538 SkAlphaType at = isOpaque ? kOpaque_SkAlphaType : kPremul_SkAlphaType; | 487 SkAlphaType at = isOpaque ? kOpaque_SkAlphaType : kPremul_SkAlphaType; |
| 539 return this->allocPixels(SkImageInfo::Make(width, height, ct, at)); | 488 return this->allocPixels(SkImageInfo::Make(width, height, ct, at)); |
| 540 } | 489 } |
| 541 | 490 |
| 542 /////////////////////////////////////////////////////////////////////////////// | 491 /////////////////////////////////////////////////////////////////////////////// |
| 543 | 492 |
| 544 void SkBitmap::freePixels() { | 493 void SkBitmap::freePixels() { |
| 545 // if we're gonna free the pixels, we certainly need to free the mipmap | |
| 546 this->freeMipMap(); | |
| 547 | |
| 548 if (NULL != fPixelRef) { | 494 if (NULL != fPixelRef) { |
| 549 if (fPixelLockCount > 0) { | 495 if (fPixelLockCount > 0) { |
| 550 fPixelRef->unlockPixels(); | 496 fPixelRef->unlockPixels(); |
| 551 } | 497 } |
| 552 fPixelRef->unref(); | 498 fPixelRef->unref(); |
| 553 fPixelRef = NULL; | 499 fPixelRef = NULL; |
| 554 fPixelRefOrigin.setZero(); | 500 fPixelRefOrigin.setZero(); |
| 555 } | 501 } |
| 556 fPixelLockCount = 0; | 502 fPixelLockCount = 0; |
| 557 fPixels = NULL; | 503 fPixels = NULL; |
| 558 fColorTable = NULL; | 504 fColorTable = NULL; |
| 559 } | 505 } |
| 560 | 506 |
| 561 void SkBitmap::freeMipMap() { | |
| 562 if (fMipMap) { | |
| 563 fMipMap->unref(); | |
| 564 fMipMap = NULL; | |
| 565 } | |
| 566 } | |
| 567 | |
| 568 uint32_t SkBitmap::getGenerationID() const { | 507 uint32_t SkBitmap::getGenerationID() const { |
| 569 return (fPixelRef) ? fPixelRef->getGenerationID() : 0; | 508 return (fPixelRef) ? fPixelRef->getGenerationID() : 0; |
| 570 } | 509 } |
| 571 | 510 |
| 572 void SkBitmap::notifyPixelsChanged() const { | 511 void SkBitmap::notifyPixelsChanged() const { |
| 573 SkASSERT(!this->isImmutable()); | 512 SkASSERT(!this->isImmutable()); |
| 574 if (fPixelRef) { | 513 if (fPixelRef) { |
| 575 fPixelRef->notifyPixelsChanged(); | 514 fPixelRef->notifyPixelsChanged(); |
| 576 } | 515 } |
| 577 } | 516 } |
| (...skipping 604 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 1182 } | 1121 } |
| 1183 | 1122 |
| 1184 if (this->getTexture()) { | 1123 if (this->getTexture()) { |
| 1185 return false; | 1124 return false; |
| 1186 } else { | 1125 } else { |
| 1187 return this->copyTo(dst, dstCT, NULL); | 1126 return this->copyTo(dst, dstCT, NULL); |
| 1188 } | 1127 } |
| 1189 } | 1128 } |
| 1190 | 1129 |
| 1191 /////////////////////////////////////////////////////////////////////////////// | 1130 /////////////////////////////////////////////////////////////////////////////// |
| 1192 /////////////////////////////////////////////////////////////////////////////// | |
| 1193 | |
| 1194 static void downsampleby2_proc32(SkBitmap* dst, int x, int y, | |
| 1195 const SkBitmap& src) { | |
| 1196 x <<= 1; | |
| 1197 y <<= 1; | |
| 1198 const SkPMColor* p = src.getAddr32(x, y); | |
| 1199 const SkPMColor* baseP = p; | |
| 1200 SkPMColor c, ag, rb; | |
| 1201 | |
| 1202 c = *p; ag = (c >> 8) & 0xFF00FF; rb = c & 0xFF00FF; | |
| 1203 if (x < src.width() - 1) { | |
| 1204 p += 1; | |
| 1205 } | |
| 1206 c = *p; ag += (c >> 8) & 0xFF00FF; rb += c & 0xFF00FF; | |
| 1207 | |
| 1208 p = baseP; | |
| 1209 if (y < src.height() - 1) { | |
| 1210 p += src.rowBytes() >> 2; | |
| 1211 } | |
| 1212 c = *p; ag += (c >> 8) & 0xFF00FF; rb += c & 0xFF00FF; | |
| 1213 if (x < src.width() - 1) { | |
| 1214 p += 1; | |
| 1215 } | |
| 1216 c = *p; ag += (c >> 8) & 0xFF00FF; rb += c & 0xFF00FF; | |
| 1217 | |
| 1218 *dst->getAddr32(x >> 1, y >> 1) = | |
| 1219 ((rb >> 2) & 0xFF00FF) | ((ag << 6) & 0xFF00FF00); | |
| 1220 } | |
| 1221 | |
| 1222 static inline uint32_t expand16(U16CPU c) { | |
| 1223 return (c & ~SK_G16_MASK_IN_PLACE) | ((c & SK_G16_MASK_IN_PLACE) << 16); | |
| 1224 } | |
| 1225 | |
| 1226 // returns dirt in the top 16bits, but we don't care, since we only | |
| 1227 // store the low 16bits. | |
| 1228 static inline U16CPU pack16(uint32_t c) { | |
| 1229 return (c & ~SK_G16_MASK_IN_PLACE) | ((c >> 16) & SK_G16_MASK_IN_PLACE); | |
| 1230 } | |
| 1231 | |
| 1232 static void downsampleby2_proc16(SkBitmap* dst, int x, int y, | |
| 1233 const SkBitmap& src) { | |
| 1234 x <<= 1; | |
| 1235 y <<= 1; | |
| 1236 const uint16_t* p = src.getAddr16(x, y); | |
| 1237 const uint16_t* baseP = p; | |
| 1238 SkPMColor c; | |
| 1239 | |
| 1240 c = expand16(*p); | |
| 1241 if (x < src.width() - 1) { | |
| 1242 p += 1; | |
| 1243 } | |
| 1244 c += expand16(*p); | |
| 1245 | |
| 1246 p = baseP; | |
| 1247 if (y < src.height() - 1) { | |
| 1248 p += src.rowBytes() >> 1; | |
| 1249 } | |
| 1250 c += expand16(*p); | |
| 1251 if (x < src.width() - 1) { | |
| 1252 p += 1; | |
| 1253 } | |
| 1254 c += expand16(*p); | |
| 1255 | |
| 1256 *dst->getAddr16(x >> 1, y >> 1) = (uint16_t)pack16(c >> 2); | |
| 1257 } | |
| 1258 | |
| 1259 static uint32_t expand4444(U16CPU c) { | |
| 1260 return (c & 0xF0F) | ((c & ~0xF0F) << 12); | |
| 1261 } | |
| 1262 | |
| 1263 static U16CPU collaps4444(uint32_t c) { | |
| 1264 return (c & 0xF0F) | ((c >> 12) & ~0xF0F); | |
| 1265 } | |
| 1266 | |
| 1267 static void downsampleby2_proc4444(SkBitmap* dst, int x, int y, | |
| 1268 const SkBitmap& src) { | |
| 1269 x <<= 1; | |
| 1270 y <<= 1; | |
| 1271 const uint16_t* p = src.getAddr16(x, y); | |
| 1272 const uint16_t* baseP = p; | |
| 1273 uint32_t c; | |
| 1274 | |
| 1275 c = expand4444(*p); | |
| 1276 if (x < src.width() - 1) { | |
| 1277 p += 1; | |
| 1278 } | |
| 1279 c += expand4444(*p); | |
| 1280 | |
| 1281 p = baseP; | |
| 1282 if (y < src.height() - 1) { | |
| 1283 p += src.rowBytes() >> 1; | |
| 1284 } | |
| 1285 c += expand4444(*p); | |
| 1286 if (x < src.width() - 1) { | |
| 1287 p += 1; | |
| 1288 } | |
| 1289 c += expand4444(*p); | |
| 1290 | |
| 1291 *dst->getAddr16(x >> 1, y >> 1) = (uint16_t)collaps4444(c >> 2); | |
| 1292 } | |
| 1293 | |
| 1294 void SkBitmap::buildMipMap(bool forceRebuild) { | |
| 1295 if (forceRebuild) | |
| 1296 this->freeMipMap(); | |
| 1297 else if (fMipMap) | |
| 1298 return; // we're already built | |
| 1299 | |
| 1300 SkASSERT(NULL == fMipMap); | |
| 1301 | |
| 1302 void (*proc)(SkBitmap* dst, int x, int y, const SkBitmap& src); | |
| 1303 | |
| 1304 const SkBitmap::Config config = this->config(); | |
| 1305 | |
| 1306 switch (config) { | |
| 1307 case kARGB_8888_Config: | |
| 1308 proc = downsampleby2_proc32; | |
| 1309 break; | |
| 1310 case kRGB_565_Config: | |
| 1311 proc = downsampleby2_proc16; | |
| 1312 break; | |
| 1313 case kARGB_4444_Config: | |
| 1314 proc = downsampleby2_proc4444; | |
| 1315 break; | |
| 1316 case kIndex8_Config: | |
| 1317 case kA8_Config: | |
| 1318 default: | |
| 1319 return; // don't build mipmaps for these configs | |
| 1320 } | |
| 1321 | |
| 1322 SkAutoLockPixels alp(*this); | |
| 1323 if (!this->readyToDraw()) { | |
| 1324 return; | |
| 1325 } | |
| 1326 | |
| 1327 // whip through our loop to compute the exact size needed | |
| 1328 size_t size = 0; | |
| 1329 int maxLevels = 0; | |
| 1330 { | |
| 1331 int width = this->width(); | |
| 1332 int height = this->height(); | |
| 1333 for (;;) { | |
| 1334 width >>= 1; | |
| 1335 height >>= 1; | |
| 1336 if (0 == width || 0 == height) { | |
| 1337 break; | |
| 1338 } | |
| 1339 size += ComputeRowBytes(config, width) * height; | |
| 1340 maxLevels += 1; | |
| 1341 } | |
| 1342 } | |
| 1343 | |
| 1344 // nothing to build | |
| 1345 if (0 == maxLevels) { | |
| 1346 return; | |
| 1347 } | |
| 1348 | |
| 1349 SkBitmap srcBM(*this); | |
| 1350 srcBM.lockPixels(); | |
| 1351 if (!srcBM.readyToDraw()) { | |
| 1352 return; | |
| 1353 } | |
| 1354 | |
| 1355 MipMap* mm = MipMap::Alloc(maxLevels, size); | |
| 1356 if (NULL == mm) { | |
| 1357 return; | |
| 1358 } | |
| 1359 | |
| 1360 MipLevel* level = mm->levels(); | |
| 1361 uint8_t* addr = (uint8_t*)mm->pixels(); | |
| 1362 int width = this->width(); | |
| 1363 int height = this->height(); | |
| 1364 uint32_t rowBytes; | |
| 1365 SkBitmap dstBM; | |
| 1366 | |
| 1367 for (int i = 0; i < maxLevels; i++) { | |
| 1368 width >>= 1; | |
| 1369 height >>= 1; | |
| 1370 rowBytes = SkToU32(ComputeRowBytes(config, width)); | |
| 1371 | |
| 1372 level[i].fPixels = addr; | |
| 1373 level[i].fWidth = width; | |
| 1374 level[i].fHeight = height; | |
| 1375 level[i].fRowBytes = rowBytes; | |
| 1376 | |
| 1377 dstBM.setConfig(config, width, height, rowBytes); | |
| 1378 dstBM.setPixels(addr); | |
| 1379 | |
| 1380 srcBM.lockPixels(); | |
| 1381 for (int y = 0; y < height; y++) { | |
| 1382 for (int x = 0; x < width; x++) { | |
| 1383 proc(&dstBM, x, y, srcBM); | |
| 1384 } | |
| 1385 } | |
| 1386 srcBM.unlockPixels(); | |
| 1387 | |
| 1388 srcBM = dstBM; | |
| 1389 addr += height * rowBytes; | |
| 1390 } | |
| 1391 SkASSERT(addr == (uint8_t*)mm->pixels() + size); | |
| 1392 fMipMap = mm; | |
| 1393 } | |
| 1394 | |
| 1395 bool SkBitmap::hasMipMap() const { | |
| 1396 return fMipMap != NULL; | |
| 1397 } | |
| 1398 | |
| 1399 int SkBitmap::extractMipLevel(SkBitmap* dst, SkFixed sx, SkFixed sy) { | |
| 1400 if (NULL == fMipMap) { | |
| 1401 return 0; | |
| 1402 } | |
| 1403 | |
| 1404 int level = ComputeMipLevel(sx, sy) >> 16; | |
| 1405 SkASSERT(level >= 0); | |
| 1406 if (level <= 0) { | |
| 1407 return 0; | |
| 1408 } | |
| 1409 | |
| 1410 if (level >= fMipMap->fLevelCount) { | |
| 1411 level = fMipMap->fLevelCount - 1; | |
| 1412 } | |
| 1413 if (dst) { | |
| 1414 const MipLevel& mip = fMipMap->levels()[level - 1]; | |
| 1415 dst->setConfig((SkBitmap::Config)this->config(), | |
| 1416 mip.fWidth, mip.fHeight, mip.fRowBytes); | |
| 1417 dst->setPixels(mip.fPixels); | |
| 1418 } | |
| 1419 return level; | |
| 1420 } | |
| 1421 | |
| 1422 SkFixed SkBitmap::ComputeMipLevel(SkFixed sx, SkFixed sy) { | |
| 1423 sx = SkAbs32(sx); | |
| 1424 sy = SkAbs32(sy); | |
| 1425 if (sx < sy) { | |
| 1426 sx = sy; | |
| 1427 } | |
| 1428 if (sx < SK_Fixed1) { | |
| 1429 return 0; | |
| 1430 } | |
| 1431 int clz = SkCLZ(sx); | |
| 1432 SkASSERT(clz >= 1 && clz <= 15); | |
| 1433 return SkIntToFixed(15 - clz) + ((unsigned)(sx << (clz + 1)) >> 16); | |
| 1434 } | |
| 1435 | |
| 1436 /////////////////////////////////////////////////////////////////////////////// | |
| 1437 | 1131 |
| 1438 static bool GetBitmapAlpha(const SkBitmap& src, uint8_t* SK_RESTRICT alpha, | 1132 static bool GetBitmapAlpha(const SkBitmap& src, uint8_t* SK_RESTRICT alpha, |
| 1439 int alphaRowBytes) { | 1133 int alphaRowBytes) { |
| 1440 SkASSERT(alpha != NULL); | 1134 SkASSERT(alpha != NULL); |
| 1441 SkASSERT(alphaRowBytes >= src.width()); | 1135 SkASSERT(alphaRowBytes >= src.width()); |
| 1442 | 1136 |
| 1443 SkBitmap::Config config = src.config(); | 1137 SkBitmap::Config config = src.config(); |
| 1444 int w = src.width(); | 1138 int w = src.width(); |
| 1445 int h = src.height(); | 1139 int h = src.height(); |
| 1446 size_t rb = src.rowBytes(); | 1140 size_t rb = src.rowBytes(); |
| (...skipping 282 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 1729 /////////////////////////////////////////////////////////////////////////////// | 1423 /////////////////////////////////////////////////////////////////////////////// |
| 1730 | 1424 |
| 1731 #ifdef SK_DEBUG | 1425 #ifdef SK_DEBUG |
| 1732 void SkImageInfo::validate() const { | 1426 void SkImageInfo::validate() const { |
| 1733 SkASSERT(fWidth >= 0); | 1427 SkASSERT(fWidth >= 0); |
| 1734 SkASSERT(fHeight >= 0); | 1428 SkASSERT(fHeight >= 0); |
| 1735 SkASSERT(SkColorTypeIsValid(fColorType)); | 1429 SkASSERT(SkColorTypeIsValid(fColorType)); |
| 1736 SkASSERT(SkAlphaTypeIsValid(fAlphaType)); | 1430 SkASSERT(SkAlphaTypeIsValid(fAlphaType)); |
| 1737 } | 1431 } |
| 1738 #endif | 1432 #endif |
| OLD | NEW |