1 /* 2 * Copyright 2010 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 "SkPDFImage.h" 9 10 #include "SkBitmap.h" 11 #include "SkColor.h" 12 #include "SkColorPriv.h" 13 #include "SkData.h" 14 #include "SkFlate.h" 15 #include "SkPDFCatalog.h" 16 #include "SkRect.h" 17 #include "SkStream.h" 18 #include "SkString.h" 19 #include "SkUnPreMultiply.h" 20 21 static const int kNoColorTransform = 0; 22 23 static bool skip_compression(SkPDFCatalog* catalog) { 24 return SkToBool(catalog->getDocumentFlags() & 25 SkPDFDocument::kFavorSpeedOverSize_Flags); 26 } 27 28 static size_t get_uncompressed_size(const SkBitmap& bitmap, 29 const SkIRect& srcRect) { 30 switch (bitmap.config()) { 31 case SkBitmap::kIndex8_Config: 32 return srcRect.width() * srcRect.height(); 33 case SkBitmap::kARGB_4444_Config: 34 return ((srcRect.width() * 3 + 1) / 2) * srcRect.height(); 35 case SkBitmap::kRGB_565_Config: 36 return srcRect.width() * 3 * srcRect.height(); 37 case SkBitmap::kARGB_8888_Config: 38 return srcRect.width() * 3 * srcRect.height(); 39 case SkBitmap::kA8_Config: 40 return 1; 41 default: 42 SkASSERT(false); 43 return 0; 44 } 45 } 46 47 static SkStream* extract_index8_image(const SkBitmap& bitmap, 48 const SkIRect& srcRect) { 49 const int rowBytes = srcRect.width(); 50 SkStream* stream = SkNEW_ARGS(SkMemoryStream, 51 (get_uncompressed_size(bitmap, srcRect))); 52 uint8_t* dst = (uint8_t*)stream->getMemoryBase(); 53 54 for (int y = srcRect.fTop; y < srcRect.fBottom; y++) { 55 memcpy(dst, bitmap.getAddr8(srcRect.fLeft, y), rowBytes); 56 dst += rowBytes; 57 } 58 return stream; 59 } 60 61 static SkStream* extract_argb4444_data(const SkBitmap& bitmap, 62 const SkIRect& srcRect, 63 bool extractAlpha, 64 bool* isOpaque, 65 bool* isTransparent) { 66 SkStream* stream; 67 uint8_t* dst = NULL; 68 if (extractAlpha) { 69 const int alphaRowBytes = (srcRect.width() + 1) / 2; 70 stream = SkNEW_ARGS(SkMemoryStream, 71 (alphaRowBytes * srcRect.height())); 72 } else { 73 stream = SkNEW_ARGS(SkMemoryStream, 74 (get_uncompressed_size(bitmap, srcRect))); 75 } 76 dst = (uint8_t*)stream->getMemoryBase(); 77 78 for (int y = srcRect.fTop; y < srcRect.fBottom; y++) { 79 uint16_t* src = bitmap.getAddr16(0, y); 80 int x; 81 for (x = srcRect.fLeft; x + 1 < srcRect.fRight; x += 2) { 82 if (extractAlpha) { 83 dst[0] = (SkGetPackedA4444(src[x]) << 4) | 84 SkGetPackedA4444(src[x + 1]); 85 *isOpaque &= dst[0] == SK_AlphaOPAQUE; 86 *isTransparent &= dst[0] == SK_AlphaTRANSPARENT; 87 dst++; 88 } else { 89 dst[0] = (SkGetPackedR4444(src[x]) << 4) | 90 SkGetPackedG4444(src[x]); 91 dst[1] = (SkGetPackedB4444(src[x]) << 4) | 92 SkGetPackedR4444(src[x + 1]); 93 dst[2] = (SkGetPackedG4444(src[x + 1]) << 4) | 94 SkGetPackedB4444(src[x + 1]); 95 dst += 3; 96 } 97 } 98 if (srcRect.width() & 1) { 99 if (extractAlpha) { 100 dst[0] = (SkGetPackedA4444(src[x]) << 4); 101 *isOpaque &= dst[0] == (SK_AlphaOPAQUE & 0xF0); 102 *isTransparent &= dst[0] == (SK_AlphaTRANSPARENT & 0xF0); 103 dst++; 104 105 } else { 106 dst[0] = (SkGetPackedR4444(src[x]) << 4) | 107 SkGetPackedG4444(src[x]); 108 dst[1] = (SkGetPackedB4444(src[x]) << 4); 109 dst += 2; 110 } 111 } 112 } 113 return stream; 114 } 115 116 static SkStream* extract_rgb565_image(const SkBitmap& bitmap, 117 const SkIRect& srcRect) { 118 SkStream* stream = SkNEW_ARGS(SkMemoryStream, 119 (get_uncompressed_size(bitmap, 120 srcRect))); 121 uint8_t* dst = (uint8_t*)stream->getMemoryBase(); 122 for (int y = srcRect.fTop; y < srcRect.fBottom; y++) { 123 uint16_t* src = bitmap.getAddr16(0, y); 124 for (int x = srcRect.fLeft; x < srcRect.fRight; x++) { 125 dst[0] = SkGetPackedR16(src[x]); 126 dst[1] = SkGetPackedG16(src[x]); 127 dst[2] = SkGetPackedB16(src[x]); 128 dst += 3; 129 } 130 } 131 return stream; 132 } 133 134 static SkStream* extract_argb8888_data(const SkBitmap& bitmap, 135 const SkIRect& srcRect, 136 bool extractAlpha, 137 bool* isOpaque, 138 bool* isTransparent) { 139 SkStream* stream; 140 if (extractAlpha) { 141 stream = SkNEW_ARGS(SkMemoryStream, 142 (srcRect.width() * srcRect.height())); 143 } else { 144 stream = SkNEW_ARGS(SkMemoryStream, 145 (get_uncompressed_size(bitmap, srcRect))); 146 } 147 uint8_t* dst = (uint8_t*)stream->getMemoryBase(); 148 149 for (int y = srcRect.fTop; y < srcRect.fBottom; y++) { 150 uint32_t* src = bitmap.getAddr32(0, y); 151 for (int x = srcRect.fLeft; x < srcRect.fRight; x++) { 152 if (extractAlpha) { 153 dst[0] = SkGetPackedA32(src[x]); 154 *isOpaque &= dst[0] == SK_AlphaOPAQUE; 155 *isTransparent &= dst[0] == SK_AlphaTRANSPARENT; 156 dst++; 157 } else { 158 dst[0] = SkGetPackedR32(src[x]); 159 dst[1] = SkGetPackedG32(src[x]); 160 dst[2] = SkGetPackedB32(src[x]); 161 dst += 3; 162 } 163 } 164 } 165 return stream; 166 } 167 168 static SkStream* extract_a8_alpha(const SkBitmap& bitmap, 169 const SkIRect& srcRect, 170 bool* isOpaque, 171 bool* isTransparent) { 172 const int alphaRowBytes = srcRect.width(); 173 SkStream* stream = SkNEW_ARGS(SkMemoryStream, 174 (alphaRowBytes * srcRect.height())); 175 uint8_t* alphaDst = (uint8_t*)stream->getMemoryBase(); 176 177 for (int y = srcRect.fTop; y < srcRect.fBottom; y++) { 178 uint8_t* src = bitmap.getAddr8(0, y); 179 for (int x = srcRect.fLeft; x < srcRect.fRight; x++) { 180 alphaDst[0] = src[x]; 181 *isOpaque &= alphaDst[0] == SK_AlphaOPAQUE; 182 *isTransparent &= alphaDst[0] == SK_AlphaTRANSPARENT; 183 alphaDst++; 184 } 185 } 186 return stream; 187 } 188 189 static SkStream* create_black_image() { 190 SkStream* stream = SkNEW_ARGS(SkMemoryStream, (1)); 191 ((uint8_t*)stream->getMemoryBase())[0] = 0; 192 return stream; 193 } 194 195 /** 196 * Extract either the color or image data from a SkBitmap into a SkStream. 197 * @param bitmap Bitmap to extract data from. 198 * @param srcRect Region in the bitmap to extract. 199 * @param extractAlpha Set to true to extract the alpha data or false to 200 * extract the color data. 201 * @param isTransparent Pointer to a bool to output whether the alpha is 202 * completely transparent. May be NULL. Only valid when 203 * extractAlpha == true. 204 * @return Unencoded image data, or NULL if either data was not 205 * available or alpha data was requested but the image was 206 * entirely transparent or opaque. 207 */ 208 static SkStream* extract_image_data(const SkBitmap& bitmap, 209 const SkIRect& srcRect, 210 bool extractAlpha, bool* isTransparent) { 211 SkBitmap::Config config = bitmap.config(); 212 if (extractAlpha && (config == SkBitmap::kIndex8_Config || 213 config == SkBitmap::kRGB_565_Config)) { 214 if (isTransparent != NULL) { 215 *isTransparent = false; 216 } 217 return NULL; 218 } 219 bool isOpaque = true; 220 bool transparent = extractAlpha; 221 SkStream* stream = NULL; 222 223 bitmap.lockPixels(); 224 switch (config) { 225 case SkBitmap::kIndex8_Config: 226 if (!extractAlpha) { 227 stream = extract_index8_image(bitmap, srcRect); 228 } 229 break; 230 case SkBitmap::kARGB_4444_Config: 231 stream = extract_argb4444_data(bitmap, srcRect, extractAlpha, 232 &isOpaque, &transparent); 233 break; 234 case SkBitmap::kRGB_565_Config: 235 if (!extractAlpha) { 236 stream = extract_rgb565_image(bitmap, srcRect); 237 } 238 break; 239 case SkBitmap::kARGB_8888_Config: 240 stream = extract_argb8888_data(bitmap, srcRect, extractAlpha, 241 &isOpaque, &transparent); 242 break; 243 case SkBitmap::kA8_Config: 244 if (!extractAlpha) { 245 stream = create_black_image(); 246 } else { 247 stream = extract_a8_alpha(bitmap, srcRect, 248 &isOpaque, &transparent); 249 } 250 break; 251 default: 252 SkASSERT(false); 253 } 254 bitmap.unlockPixels(); 255 256 if (isTransparent != NULL) { 257 *isTransparent = transparent; 258 } 259 if (extractAlpha && (transparent || isOpaque)) { 260 SkSafeUnref(stream); 261 return NULL; 262 } 263 return stream; 264 } 265 266 static SkPDFArray* make_indexed_color_space(SkColorTable* table) { 267 SkPDFArray* result = new SkPDFArray(); 268 result->reserve(4); 269 result->appendName("Indexed"); 270 result->appendName("DeviceRGB"); 271 result->appendInt(table->count() - 1); 272 273 // Potentially, this could be represented in fewer bytes with a stream. 274 // Max size as a string is 1.5k. 275 SkString index; 276 for (int i = 0; i < table->count(); i++) { 277 char buf[3]; 278 SkColor color = SkUnPreMultiply::PMColorToColor((*table)[i]); 279 buf[0] = SkGetPackedR32(color); 280 buf[1] = SkGetPackedG32(color); 281 buf[2] = SkGetPackedB32(color); 282 index.append(buf, 3); 283 } 284 result->append(new SkPDFString(index))->unref(); 285 return result; 286 } 287 288 /** 289 * Removes the alpha component of an ARGB color (including unpremultiply) while 290 * keeping the output in the same format as the input. 291 */ 292 static uint32_t remove_alpha_argb8888(uint32_t pmColor) { 293 SkColor color = SkUnPreMultiply::PMColorToColor(pmColor); 294 return SkPackARGB32NoCheck(SK_AlphaOPAQUE, 295 SkColorGetR(color), 296 SkColorGetG(color), 297 SkColorGetB(color)); 298 } 299 300 static uint16_t remove_alpha_argb4444(uint16_t pmColor) { 301 return SkPixel32ToPixel4444( 302 remove_alpha_argb8888(SkPixel4444ToPixel32(pmColor))); 303 } 304 305 static uint32_t get_argb8888_neighbor_avg_color(const SkBitmap& bitmap, 306 int xOrig, int yOrig) { 307 uint8_t count = 0; 308 uint16_t r = 0; 309 uint16_t g = 0; 310 uint16_t b = 0; 311 312 for (int y = yOrig - 1; y <= yOrig + 1; y++) { 313 if (y < 0 || y >= bitmap.height()) { 314 continue; 315 } 316 uint32_t* src = bitmap.getAddr32(0, y); 317 for (int x = xOrig - 1; x <= xOrig + 1; x++) { 318 if (x < 0 || x >= bitmap.width()) { 319 continue; 320 } 321 if (SkGetPackedA32(src[x]) != SK_AlphaTRANSPARENT) { 322 uint32_t color = remove_alpha_argb8888(src[x]); 323 r += SkGetPackedR32(color); 324 g += SkGetPackedG32(color); 325 b += SkGetPackedB32(color); 326 count++; 327 } 328 } 329 } 330 331 if (count == 0) { 332 return SkPackARGB32NoCheck(SK_AlphaOPAQUE, 0, 0, 0); 333 } else { 334 return SkPackARGB32NoCheck(SK_AlphaOPAQUE, 335 r / count, g / count, b / count); 336 } 337 } 338 339 static uint16_t get_argb4444_neighbor_avg_color(const SkBitmap& bitmap, 340 int xOrig, int yOrig) { 341 uint8_t count = 0; 342 uint8_t r = 0; 343 uint8_t g = 0; 344 uint8_t b = 0; 345 346 for (int y = yOrig - 1; y <= yOrig + 1; y++) { 347 if (y < 0 || y >= bitmap.height()) { 348 continue; 349 } 350 uint16_t* src = bitmap.getAddr16(0, y); 351 for (int x = xOrig - 1; x <= xOrig + 1; x++) { 352 if (x < 0 || x >= bitmap.width()) { 353 continue; 354 } 355 if ((SkGetPackedA4444(src[x]) & 0x0F) != SK_AlphaTRANSPARENT) { 356 uint16_t color = remove_alpha_argb4444(src[x]); 357 r += SkGetPackedR4444(color); 358 g += SkGetPackedG4444(color); 359 b += SkGetPackedB4444(color); 360 count++; 361 } 362 } 363 } 364 365 if (count == 0) { 366 return SkPackARGB4444(SK_AlphaOPAQUE & 0x0F, 0, 0, 0); 367 } else { 368 return SkPackARGB4444(SK_AlphaOPAQUE & 0x0F, 369 r / count, g / count, b / count); 370 } 371 } 372 373 static SkBitmap unpremultiply_bitmap(const SkBitmap& bitmap, 374 const SkIRect& srcRect) { 375 SkBitmap outBitmap; 376 outBitmap.setConfig(bitmap.config(), srcRect.width(), srcRect.height()); 377 outBitmap.allocPixels(); 378 int dstRow = 0; 379 380 outBitmap.lockPixels(); 381 bitmap.lockPixels(); 382 switch (bitmap.config()) { 383 case SkBitmap::kARGB_4444_Config: { 384 for (int y = srcRect.fTop; y < srcRect.fBottom; y++) { 385 uint16_t* dst = outBitmap.getAddr16(0, dstRow); 386 uint16_t* src = bitmap.getAddr16(0, y); 387 for (int x = srcRect.fLeft; x < srcRect.fRight; x++) { 388 uint8_t a = SkGetPackedA4444(src[x]); 389 // It is necessary to average the color component of 390 // transparent pixels with their surrounding neighbors 391 // since the PDF renderer may separately re-sample the 392 // alpha and color channels when the image is not 393 // displayed at its native resolution. Since an alpha of 394 // zero gives no information about the color component, 395 // the pathological case is a white image with sharp 396 // transparency bounds - the color channel goes to black, 397 // and the should-be-transparent pixels are rendered 398 // as grey because of the separate soft mask and color 399 // resizing. 400 if (a == (SK_AlphaTRANSPARENT & 0x0F)) { 401 *dst = get_argb4444_neighbor_avg_color(bitmap, x, y); 402 } else { 403 *dst = remove_alpha_argb4444(src[x]); 404 } 405 dst++; 406 } 407 dstRow++; 408 } 409 break; 410 } 411 case SkBitmap::kARGB_8888_Config: { 412 for (int y = srcRect.fTop; y < srcRect.fBottom; y++) { 413 uint32_t* dst = outBitmap.getAddr32(0, dstRow); 414 uint32_t* src = bitmap.getAddr32(0, y); 415 for (int x = srcRect.fLeft; x < srcRect.fRight; x++) { 416 uint8_t a = SkGetPackedA32(src[x]); 417 if (a == SK_AlphaTRANSPARENT) { 418 *dst = get_argb8888_neighbor_avg_color(bitmap, x, y); 419 } else { 420 *dst = remove_alpha_argb8888(src[x]); 421 } 422 dst++; 423 } 424 dstRow++; 425 } 426 break; 427 } 428 default: 429 SkASSERT(false); 430 } 431 bitmap.unlockPixels(); 432 outBitmap.unlockPixels(); 433 434 outBitmap.setImmutable(); 435 436 return outBitmap; 437 } 438 439 // static 440 SkPDFImage* SkPDFImage::CreateImage(const SkBitmap& bitmap, 441 const SkIRect& srcRect, 442 SkPicture::EncodeBitmap encoder) { 443 if (bitmap.config() == SkBitmap::kNo_Config) { 444 return NULL; 445 } 446 447 bool isTransparent = false; 448 SkAutoTUnref<SkStream> alphaData; 449 if (!bitmap.isOpaque()) { 450 // Note that isOpaque is not guaranteed to return false for bitmaps 451 // with alpha support but a completely opaque alpha channel, 452 // so alphaData may still be NULL if we have a completely opaque 453 // (or transparent) bitmap. 454 alphaData.reset( 455 extract_image_data(bitmap, srcRect, true, &isTransparent)); 456 } 457 if (isTransparent) { 458 return NULL; 459 } 460 461 SkPDFImage* image; 462 SkBitmap::Config config = bitmap.config(); 463 if (alphaData.get() != NULL && (config == SkBitmap::kARGB_8888_Config || 464 config == SkBitmap::kARGB_4444_Config)) { 465 SkBitmap unpremulBitmap = unpremultiply_bitmap(bitmap, srcRect); 466 image = SkNEW_ARGS(SkPDFImage, (NULL, unpremulBitmap, false, 467 SkIRect::MakeWH(srcRect.width(), srcRect.height()), 468 encoder)); 469 } else { 470 image = SkNEW_ARGS(SkPDFImage, (NULL, bitmap, false, srcRect, encoder)); 471 } 472 if (alphaData.get() != NULL) { 473 SkAutoTUnref<SkPDFImage> mask( 474 SkNEW_ARGS(SkPDFImage, (alphaData.get(), bitmap, 475 true, srcRect, NULL))); 476 image->addSMask(mask); 477 } 478 479 return image; 480 } 481 482 SkPDFImage::~SkPDFImage() { 483 fResources.unrefAll(); 484 } 485 486 SkPDFImage* SkPDFImage::addSMask(SkPDFImage* mask) { 487 fResources.push(mask); 488 mask->ref(); 489 insert("SMask", new SkPDFObjRef(mask))->unref(); 490 return mask; 491 } 492 493 void SkPDFImage::getResources(const SkTSet<SkPDFObject*>& knownResourceObjects, 494 SkTSet<SkPDFObject*>* newResourceObjects) { 495 GetResourcesHelper(&fResources, knownResourceObjects, newResourceObjects); 496 } 497 498 SkPDFImage::SkPDFImage(SkStream* stream, 499 const SkBitmap& bitmap, 500 bool isAlpha, 501 const SkIRect& srcRect, 502 SkPicture::EncodeBitmap encoder) 503 : fIsAlpha(isAlpha), 504 fSrcRect(srcRect), 505 fEncoder(encoder) { 506 507 if (bitmap.isImmutable()) { 508 fBitmap = bitmap; 509 } else { 510 bitmap.deepCopyTo(&fBitmap, bitmap.config()); 511 fBitmap.setImmutable(); 512 } 513 514 if (stream != NULL) { 515 setData(stream); 516 fStreamValid = true; 517 } else { 518 fStreamValid = false; 519 } 520 521 SkBitmap::Config config = fBitmap.config(); 522 523 insertName("Type", "XObject"); 524 insertName("Subtype", "Image"); 525 526 bool alphaOnly = (config == SkBitmap::kA8_Config); 527 528 if (!isAlpha && alphaOnly) { 529 // For alpha only images, we stretch a single pixel of black for 530 // the color/shape part. 531 SkAutoTUnref<SkPDFInt> one(new SkPDFInt(1)); 532 insert("Width", one.get()); 533 insert("Height", one.get()); 534 } else { 535 insertInt("Width", fSrcRect.width()); 536 insertInt("Height", fSrcRect.height()); 537 } 538 539 if (isAlpha || alphaOnly) { 540 insertName("ColorSpace", "DeviceGray"); 541 } else if (config == SkBitmap::kIndex8_Config) { 542 SkAutoLockPixels alp(fBitmap); 543 insert("ColorSpace", 544 make_indexed_color_space(fBitmap.getColorTable()))->unref(); 545 } else { 546 insertName("ColorSpace", "DeviceRGB"); 547 } 548 549 int bitsPerComp = 8; 550 if (config == SkBitmap::kARGB_4444_Config) { 551 bitsPerComp = 4; 552 } 553 insertInt("BitsPerComponent", bitsPerComp); 554 555 if (config == SkBitmap::kRGB_565_Config) { 556 SkASSERT(!isAlpha); 557 SkAutoTUnref<SkPDFInt> zeroVal(new SkPDFInt(0)); 558 SkAutoTUnref<SkPDFScalar> scale5Val( 559 new SkPDFScalar(8.2258f)); // 255/2^5-1 560 SkAutoTUnref<SkPDFScalar> scale6Val( 561 new SkPDFScalar(4.0476f)); // 255/2^6-1 562 SkAutoTUnref<SkPDFArray> decodeValue(new SkPDFArray()); 563 decodeValue->reserve(6); 564 decodeValue->append(zeroVal.get()); 565 decodeValue->append(scale5Val.get()); 566 decodeValue->append(zeroVal.get()); 567 decodeValue->append(scale6Val.get()); 568 decodeValue->append(zeroVal.get()); 569 decodeValue->append(scale5Val.get()); 570 insert("Decode", decodeValue.get()); 571 } 572 } 573 574 SkPDFImage::SkPDFImage(SkPDFImage& pdfImage) 575 : SkPDFStream(pdfImage), 576 fBitmap(pdfImage.fBitmap), 577 fIsAlpha(pdfImage.fIsAlpha), 578 fSrcRect(pdfImage.fSrcRect), 579 fEncoder(pdfImage.fEncoder), 580 fStreamValid(pdfImage.fStreamValid) { 581 // Nothing to do here - the image params are already copied in SkPDFStream's 582 // constructor, and the bitmap will be regenerated and encoded in 583 // populate. 584 } 585 586 bool SkPDFImage::populate(SkPDFCatalog* catalog) { 587 if (getState() == kUnused_State) { 588 // Initializing image data for the first time. 589 SkDynamicMemoryWStream dctCompressedWStream; 590 if (!skip_compression(catalog) && fEncoder && 591 get_uncompressed_size(fBitmap, fSrcRect) > 1) { 592 SkBitmap subset; 593 // Extract subset 594 if (!fBitmap.extractSubset(&subset, fSrcRect)) { 595 // TODO(edisonn) It fails only for kA1_Config, if that is a 596 // major concern we will fix it later, so far it is NYI. 597 return false; 598 } 599 size_t pixelRefOffset = 0; 600 SkAutoTUnref<SkData> data(fEncoder(&pixelRefOffset, subset)); 601 if (data.get() && data->size() < get_uncompressed_size(fBitmap, 602 fSrcRect)) { 603 SkAutoTUnref<SkStream> stream(SkNEW_ARGS(SkMemoryStream, 604 (data))); 605 setData(stream.get()); 606 607 insertName("Filter", "DCTDecode"); 608 insertInt("ColorTransform", kNoColorTransform); 609 insertInt("Length", getData()->getLength()); 610 setState(kCompressed_State); 611 return true; 612 } 613 } 614 // Fallback method 615 if (!fStreamValid) { 616 SkAutoTUnref<SkStream> stream( 617 extract_image_data(fBitmap, fSrcRect, fIsAlpha, NULL)); 618 setData(stream); 619 fStreamValid = true; 620 } 621 return INHERITED::populate(catalog); 622 } else if (getState() == kNoCompression_State && 623 !skip_compression(catalog) && 624 (SkFlate::HaveFlate() || fEncoder)) { 625 // Compression has not been requested when the stream was first created, 626 // but the new catalog wants it compressed. 627 if (!getSubstitute()) { 628 SkPDFStream* substitute = SkNEW_ARGS(SkPDFImage, (*this)); 629 setSubstitute(substitute); 630 catalog->setSubstitute(this, substitute); 631 } 632 return false; 633 } 634 return true; 635 } 636