Bitmap.cpp revision a2341a9f6addcd79723965ec5b1a1c5ae0f8bd65
1#include "SkBitmap.h" 2#include "SkImageEncoder.h" 3#include "SkColorPriv.h" 4#include "GraphicsJNI.h" 5#include "SkDither.h" 6#include "SkUnPreMultiply.h" 7 8#include <binder/Parcel.h> 9#include "android_util_Binder.h" 10#include "android_nio_utils.h" 11#include "CreateJavaOutputStreamAdaptor.h" 12 13#include <jni.h> 14 15#include <Caches.h> 16 17#if 0 18 #define TRACE_BITMAP(code) code 19#else 20 #define TRACE_BITMAP(code) 21#endif 22 23/////////////////////////////////////////////////////////////////////////////// 24// Conversions to/from SkColor, for get/setPixels, and the create method, which 25// is basically like setPixels 26 27typedef void (*FromColorProc)(void* dst, const SkColor src[], int width, 28 int x, int y); 29 30static void FromColor_D32(void* dst, const SkColor src[], int width, 31 int, int) { 32 SkPMColor* d = (SkPMColor*)dst; 33 34 for (int i = 0; i < width; i++) { 35 *d++ = SkPreMultiplyColor(*src++); 36 } 37} 38 39static void FromColor_D565(void* dst, const SkColor src[], int width, 40 int x, int y) { 41 uint16_t* d = (uint16_t*)dst; 42 43 DITHER_565_SCAN(y); 44 for (int stop = x + width; x < stop; x++) { 45 SkColor c = *src++; 46 *d++ = SkDitherRGBTo565(SkColorGetR(c), SkColorGetG(c), SkColorGetB(c), 47 DITHER_VALUE(x)); 48 } 49} 50 51static void FromColor_D4444(void* dst, const SkColor src[], int width, 52 int x, int y) { 53 SkPMColor16* d = (SkPMColor16*)dst; 54 55 DITHER_4444_SCAN(y); 56 for (int stop = x + width; x < stop; x++) { 57 SkPMColor c = SkPreMultiplyColor(*src++); 58 *d++ = SkDitherARGB32To4444(c, DITHER_VALUE(x)); 59// *d++ = SkPixel32ToPixel4444(c); 60 } 61} 62 63// can return NULL 64static FromColorProc ChooseFromColorProc(SkBitmap::Config config) { 65 switch (config) { 66 case SkBitmap::kARGB_8888_Config: 67 return FromColor_D32; 68 case SkBitmap::kARGB_4444_Config: 69 return FromColor_D4444; 70 case SkBitmap::kRGB_565_Config: 71 return FromColor_D565; 72 default: 73 break; 74 } 75 return NULL; 76} 77 78bool GraphicsJNI::SetPixels(JNIEnv* env, jintArray srcColors, 79 int srcOffset, int srcStride, 80 int x, int y, int width, int height, 81 const SkBitmap& dstBitmap) { 82 SkAutoLockPixels alp(dstBitmap); 83 void* dst = dstBitmap.getPixels(); 84 FromColorProc proc = ChooseFromColorProc(dstBitmap.config()); 85 86 if (NULL == dst || NULL == proc) { 87 return false; 88 } 89 90 const jint* array = env->GetIntArrayElements(srcColors, NULL); 91 const SkColor* src = (const SkColor*)array + srcOffset; 92 93 // reset to to actual choice from caller 94 dst = dstBitmap.getAddr(x, y); 95 // now copy/convert each scanline 96 for (int y = 0; y < height; y++) { 97 proc(dst, src, width, x, y); 98 src += srcStride; 99 dst = (char*)dst + dstBitmap.rowBytes(); 100 } 101 102 env->ReleaseIntArrayElements(srcColors, const_cast<jint*>(array), 103 JNI_ABORT); 104 return true; 105} 106 107//////////////////// ToColor procs 108 109typedef void (*ToColorProc)(SkColor dst[], const void* src, int width, 110 SkColorTable*); 111 112static void ToColor_S32_Alpha(SkColor dst[], const void* src, int width, 113 SkColorTable*) { 114 SkASSERT(width > 0); 115 const SkPMColor* s = (const SkPMColor*)src; 116 do { 117 *dst++ = SkUnPreMultiply::PMColorToColor(*s++); 118 } while (--width != 0); 119} 120 121static void ToColor_S32_Opaque(SkColor dst[], const void* src, int width, 122 SkColorTable*) { 123 SkASSERT(width > 0); 124 const SkPMColor* s = (const SkPMColor*)src; 125 do { 126 SkPMColor c = *s++; 127 *dst++ = SkColorSetRGB(SkGetPackedR32(c), SkGetPackedG32(c), 128 SkGetPackedB32(c)); 129 } while (--width != 0); 130} 131 132static void ToColor_S4444_Alpha(SkColor dst[], const void* src, int width, 133 SkColorTable*) { 134 SkASSERT(width > 0); 135 const SkPMColor16* s = (const SkPMColor16*)src; 136 do { 137 *dst++ = SkUnPreMultiply::PMColorToColor(SkPixel4444ToPixel32(*s++)); 138 } while (--width != 0); 139} 140 141static void ToColor_S4444_Opaque(SkColor dst[], const void* src, int width, 142 SkColorTable*) { 143 SkASSERT(width > 0); 144 const SkPMColor* s = (const SkPMColor*)src; 145 do { 146 SkPMColor c = SkPixel4444ToPixel32(*s++); 147 *dst++ = SkColorSetRGB(SkGetPackedR32(c), SkGetPackedG32(c), 148 SkGetPackedB32(c)); 149 } while (--width != 0); 150} 151 152static void ToColor_S565(SkColor dst[], const void* src, int width, 153 SkColorTable*) { 154 SkASSERT(width > 0); 155 const uint16_t* s = (const uint16_t*)src; 156 do { 157 uint16_t c = *s++; 158 *dst++ = SkColorSetRGB(SkPacked16ToR32(c), SkPacked16ToG32(c), 159 SkPacked16ToB32(c)); 160 } while (--width != 0); 161} 162 163static void ToColor_SI8_Alpha(SkColor dst[], const void* src, int width, 164 SkColorTable* ctable) { 165 SkASSERT(width > 0); 166 const uint8_t* s = (const uint8_t*)src; 167 const SkPMColor* colors = ctable->lockColors(); 168 do { 169 *dst++ = SkUnPreMultiply::PMColorToColor(colors[*s++]); 170 } while (--width != 0); 171 ctable->unlockColors(false); 172} 173 174static void ToColor_SI8_Opaque(SkColor dst[], const void* src, int width, 175 SkColorTable* ctable) { 176 SkASSERT(width > 0); 177 const uint8_t* s = (const uint8_t*)src; 178 const SkPMColor* colors = ctable->lockColors(); 179 do { 180 SkPMColor c = colors[*s++]; 181 *dst++ = SkColorSetRGB(SkGetPackedR32(c), SkGetPackedG32(c), 182 SkGetPackedB32(c)); 183 } while (--width != 0); 184 ctable->unlockColors(false); 185} 186 187// can return NULL 188static ToColorProc ChooseToColorProc(const SkBitmap& src) { 189 switch (src.config()) { 190 case SkBitmap::kARGB_8888_Config: 191 return src.isOpaque() ? ToColor_S32_Opaque : ToColor_S32_Alpha; 192 case SkBitmap::kARGB_4444_Config: 193 return src.isOpaque() ? ToColor_S4444_Opaque : ToColor_S4444_Alpha; 194 case SkBitmap::kRGB_565_Config: 195 return ToColor_S565; 196 case SkBitmap::kIndex8_Config: 197 if (src.getColorTable() == NULL) { 198 return NULL; 199 } 200 return src.isOpaque() ? ToColor_SI8_Opaque : ToColor_SI8_Alpha; 201 default: 202 break; 203 } 204 return NULL; 205} 206 207/////////////////////////////////////////////////////////////////////////////// 208/////////////////////////////////////////////////////////////////////////////// 209 210static jobject Bitmap_creator(JNIEnv* env, jobject, jintArray jColors, 211 int offset, int stride, int width, int height, 212 SkBitmap::Config config, jboolean isMutable) { 213 if (width <= 0 || height <= 0) { 214 doThrowIAE(env, "width and height must be > 0"); 215 return NULL; 216 } 217 218 if (NULL != jColors) { 219 size_t n = env->GetArrayLength(jColors); 220 if (n < SkAbs32(stride) * (size_t)height) { 221 doThrowAIOOBE(env); 222 return NULL; 223 } 224 } 225 226 SkBitmap bitmap; 227 228 bitmap.setConfig(config, width, height); 229 if (!GraphicsJNI::setJavaPixelRef(env, &bitmap, NULL, true)) { 230 return NULL; 231 } 232 233 if (jColors != NULL) { 234 GraphicsJNI::SetPixels(env, jColors, offset, stride, 235 0, 0, width, height, bitmap); 236 } 237 238 return GraphicsJNI::createBitmap(env, new SkBitmap(bitmap), isMutable, 239 NULL); 240} 241 242static jobject Bitmap_copy(JNIEnv* env, jobject, const SkBitmap* src, 243 SkBitmap::Config dstConfig, jboolean isMutable) { 244 SkBitmap result; 245 JavaPixelAllocator allocator(env, true); 246 247 if (!src->copyTo(&result, dstConfig, &allocator)) { 248 return NULL; 249 } 250 251 return GraphicsJNI::createBitmap(env, new SkBitmap(result), isMutable, 252 NULL); 253} 254 255static void Bitmap_destructor(JNIEnv* env, jobject, SkBitmap* bitmap) { 256#ifdef USE_OPENGL_RENDERER 257 if (android::uirenderer::Caches::hasInstance()) { 258 android::uirenderer::Caches::getInstance().textureCache.remove(bitmap); 259 } 260#endif 261 delete bitmap; 262} 263 264static void Bitmap_recycle(JNIEnv* env, jobject, SkBitmap* bitmap) { 265 bitmap->setPixels(NULL, NULL); 266} 267 268// These must match the int values in Bitmap.java 269enum JavaEncodeFormat { 270 kJPEG_JavaEncodeFormat = 0, 271 kPNG_JavaEncodeFormat = 1 272}; 273 274static bool Bitmap_compress(JNIEnv* env, jobject clazz, SkBitmap* bitmap, 275 int format, int quality, 276 jobject jstream, jbyteArray jstorage) { 277 SkImageEncoder::Type fm; 278 279 switch (format) { 280 case kJPEG_JavaEncodeFormat: 281 fm = SkImageEncoder::kJPEG_Type; 282 break; 283 case kPNG_JavaEncodeFormat: 284 fm = SkImageEncoder::kPNG_Type; 285 break; 286 default: 287 return false; 288 } 289 290 bool success = false; 291 SkWStream* strm = CreateJavaOutputStreamAdaptor(env, jstream, jstorage); 292 if (NULL != strm) { 293 SkImageEncoder* encoder = SkImageEncoder::Create(fm); 294 if (NULL != encoder) { 295 success = encoder->encodeStream(strm, *bitmap, quality); 296 delete encoder; 297 } 298 delete strm; 299 } 300 return success; 301} 302 303static void Bitmap_erase(JNIEnv* env, jobject, SkBitmap* bitmap, jint color) { 304 bitmap->eraseColor(color); 305} 306 307static int Bitmap_width(JNIEnv* env, jobject, SkBitmap* bitmap) { 308 return bitmap->width(); 309} 310 311static int Bitmap_height(JNIEnv* env, jobject, SkBitmap* bitmap) { 312 return bitmap->height(); 313} 314 315static int Bitmap_rowBytes(JNIEnv* env, jobject, SkBitmap* bitmap) { 316 return bitmap->rowBytes(); 317} 318 319static int Bitmap_config(JNIEnv* env, jobject, SkBitmap* bitmap) { 320 return bitmap->config(); 321} 322 323static int Bitmap_getGenerationId(JNIEnv* env, jobject, SkBitmap* bitmap) { 324 return bitmap->getGenerationID(); 325} 326 327static jboolean Bitmap_hasAlpha(JNIEnv* env, jobject, SkBitmap* bitmap) { 328 return !bitmap->isOpaque(); 329} 330 331static void Bitmap_setHasAlpha(JNIEnv* env, jobject, SkBitmap* bitmap, 332 jboolean hasAlpha) { 333 bitmap->setIsOpaque(!hasAlpha); 334} 335 336/////////////////////////////////////////////////////////////////////////////// 337 338static jobject Bitmap_createFromParcel(JNIEnv* env, jobject, jobject parcel) { 339 if (parcel == NULL) { 340 SkDebugf("-------- unparcel parcel is NULL\n"); 341 return NULL; 342 } 343 344 android::Parcel* p = android::parcelForJavaObject(env, parcel); 345 346 const bool isMutable = p->readInt32() != 0; 347 const SkBitmap::Config config = (SkBitmap::Config)p->readInt32(); 348 const int width = p->readInt32(); 349 const int height = p->readInt32(); 350 const int rowBytes = p->readInt32(); 351 const int density = p->readInt32(); 352 353 if (SkBitmap::kARGB_8888_Config != config && 354 SkBitmap::kRGB_565_Config != config && 355 SkBitmap::kARGB_4444_Config != config && 356 SkBitmap::kIndex8_Config != config && 357 SkBitmap::kA8_Config != config) { 358 SkDebugf("Bitmap_createFromParcel unknown config: %d\n", config); 359 return NULL; 360 } 361 362 SkBitmap* bitmap = new SkBitmap; 363 364 bitmap->setConfig(config, width, height, rowBytes); 365 366 SkColorTable* ctable = NULL; 367 if (config == SkBitmap::kIndex8_Config) { 368 int count = p->readInt32(); 369 if (count > 0) { 370 size_t size = count * sizeof(SkPMColor); 371 const SkPMColor* src = (const SkPMColor*)p->readInplace(size); 372 ctable = new SkColorTable(src, count); 373 } 374 } 375 376 if (!GraphicsJNI::setJavaPixelRef(env, bitmap, ctable, true)) { 377 ctable->safeUnref(); 378 delete bitmap; 379 return NULL; 380 } 381 382 ctable->safeUnref(); 383 384 size_t size = bitmap->getSize(); 385 bitmap->lockPixels(); 386 memcpy(bitmap->getPixels(), p->readInplace(size), size); 387 bitmap->unlockPixels(); 388 389 return GraphicsJNI::createBitmap(env, bitmap, isMutable, NULL, density); 390} 391 392static jboolean Bitmap_writeToParcel(JNIEnv* env, jobject, 393 const SkBitmap* bitmap, 394 jboolean isMutable, jint density, 395 jobject parcel) { 396 if (parcel == NULL) { 397 SkDebugf("------- writeToParcel null parcel\n"); 398 return false; 399 } 400 401 android::Parcel* p = android::parcelForJavaObject(env, parcel); 402 403 p->writeInt32(isMutable); 404 p->writeInt32(bitmap->config()); 405 p->writeInt32(bitmap->width()); 406 p->writeInt32(bitmap->height()); 407 p->writeInt32(bitmap->rowBytes()); 408 p->writeInt32(density); 409 410 if (bitmap->getConfig() == SkBitmap::kIndex8_Config) { 411 SkColorTable* ctable = bitmap->getColorTable(); 412 if (ctable != NULL) { 413 int count = ctable->count(); 414 p->writeInt32(count); 415 memcpy(p->writeInplace(count * sizeof(SkPMColor)), 416 ctable->lockColors(), count * sizeof(SkPMColor)); 417 ctable->unlockColors(false); 418 } else { 419 p->writeInt32(0); // indicate no ctable 420 } 421 } 422 423 size_t size = bitmap->getSize(); 424 bitmap->lockPixels(); 425 memcpy(p->writeInplace(size), bitmap->getPixels(), size); 426 bitmap->unlockPixels(); 427 return true; 428} 429 430static jobject Bitmap_extractAlpha(JNIEnv* env, jobject clazz, 431 const SkBitmap* src, const SkPaint* paint, 432 jintArray offsetXY) { 433 SkIPoint offset; 434 SkBitmap* dst = new SkBitmap; 435 436 src->extractAlpha(dst, paint, &offset); 437 if (offsetXY != 0 && env->GetArrayLength(offsetXY) >= 2) { 438 int* array = env->GetIntArrayElements(offsetXY, NULL); 439 array[0] = offset.fX; 440 array[1] = offset.fY; 441 env->ReleaseIntArrayElements(offsetXY, array, 0); 442 } 443 444 return GraphicsJNI::createBitmap(env, dst, true, NULL); 445} 446 447/////////////////////////////////////////////////////////////////////////////// 448 449static int Bitmap_getPixel(JNIEnv* env, jobject, const SkBitmap* bitmap, 450 int x, int y) { 451 SkAutoLockPixels alp(*bitmap); 452 453 ToColorProc proc = ChooseToColorProc(*bitmap); 454 if (NULL == proc) { 455 return 0; 456 } 457 const void* src = bitmap->getAddr(x, y); 458 if (NULL == src) { 459 return 0; 460 } 461 462 SkColor dst[1]; 463 proc(dst, src, 1, bitmap->getColorTable()); 464 return dst[0]; 465} 466 467static void Bitmap_getPixels(JNIEnv* env, jobject, const SkBitmap* bitmap, 468 jintArray pixelArray, int offset, int stride, 469 int x, int y, int width, int height) { 470 SkAutoLockPixels alp(*bitmap); 471 472 ToColorProc proc = ChooseToColorProc(*bitmap); 473 if (NULL == proc) { 474 return; 475 } 476 const void* src = bitmap->getAddr(x, y); 477 if (NULL == src) { 478 return; 479 } 480 481 SkColorTable* ctable = bitmap->getColorTable(); 482 jint* dst = env->GetIntArrayElements(pixelArray, NULL); 483 SkColor* d = (SkColor*)dst + offset; 484 while (--height >= 0) { 485 proc(d, src, width, ctable); 486 d += stride; 487 src = (void*)((const char*)src + bitmap->rowBytes()); 488 } 489 env->ReleaseIntArrayElements(pixelArray, dst, 0); 490} 491 492/////////////////////////////////////////////////////////////////////////////// 493 494static void Bitmap_setPixel(JNIEnv* env, jobject, const SkBitmap* bitmap, 495 int x, int y, SkColor color) { 496 SkAutoLockPixels alp(*bitmap); 497 if (NULL == bitmap->getPixels()) { 498 return; 499 } 500 501 FromColorProc proc = ChooseFromColorProc(bitmap->config()); 502 if (NULL == proc) { 503 return; 504 } 505 506 proc(bitmap->getAddr(x, y), &color, 1, x, y); 507} 508 509static void Bitmap_setPixels(JNIEnv* env, jobject, const SkBitmap* bitmap, 510 jintArray pixelArray, int offset, int stride, 511 int x, int y, int width, int height) { 512 GraphicsJNI::SetPixels(env, pixelArray, offset, stride, 513 x, y, width, height, *bitmap); 514} 515 516static void Bitmap_copyPixelsToBuffer(JNIEnv* env, jobject, 517 const SkBitmap* bitmap, jobject jbuffer) { 518 SkAutoLockPixels alp(*bitmap); 519 const void* src = bitmap->getPixels(); 520 521 if (NULL != src) { 522 android::AutoBufferPointer abp(env, jbuffer, JNI_TRUE); 523 524 // the java side has already checked that buffer is large enough 525 memcpy(abp.pointer(), src, bitmap->getSize()); 526 } 527} 528 529static void Bitmap_copyPixelsFromBuffer(JNIEnv* env, jobject, 530 const SkBitmap* bitmap, jobject jbuffer) { 531 SkAutoLockPixels alp(*bitmap); 532 void* dst = bitmap->getPixels(); 533 534 if (NULL != dst) { 535 android::AutoBufferPointer abp(env, jbuffer, JNI_FALSE); 536 // the java side has already checked that buffer is large enough 537 memcpy(dst, abp.pointer(), bitmap->getSize()); 538 } 539} 540 541static bool Bitmap_sameAs(JNIEnv* env, jobject, const SkBitmap* bm0, 542 const SkBitmap* bm1) { 543 if (bm0->width() != bm1->width() || 544 bm0->height() != bm1->height() || 545 bm0->config() != bm1->config()) { 546 return false; 547 } 548 549 SkAutoLockPixels alp0(*bm0); 550 SkAutoLockPixels alp1(*bm1); 551 552 // if we can't load the pixels, return false 553 if (NULL == bm0->getPixels() || NULL == bm1->getPixels()) { 554 return false; 555 } 556 557 if (bm0->config() == SkBitmap::kIndex8_Config) { 558 SkColorTable* ct0 = bm0->getColorTable(); 559 SkColorTable* ct1 = bm1->getColorTable(); 560 if (NULL == ct0 || NULL == ct1) { 561 return false; 562 } 563 if (ct0->count() != ct1->count()) { 564 return false; 565 } 566 567 SkAutoLockColors alc0(ct0); 568 SkAutoLockColors alc1(ct1); 569 const size_t size = ct0->count() * sizeof(SkPMColor); 570 if (memcmp(alc0.colors(), alc1.colors(), size) != 0) { 571 return false; 572 } 573 } 574 575 // now compare each scanline. We can't do the entire buffer at once, 576 // since we don't care about the pixel values that might extend beyond 577 // the width (since the scanline might be larger than the logical width) 578 const int h = bm0->height(); 579 const size_t size = bm0->width() * bm0->bytesPerPixel(); 580 for (int y = 0; y < h; y++) { 581 if (memcmp(bm0->getAddr(0, y), bm1->getAddr(0, y), size) != 0) { 582 return false; 583 } 584 } 585 return true; 586} 587 588static void Bitmap_prepareToDraw(JNIEnv* env, jobject, SkBitmap* bitmap) { 589 bitmap->lockPixels(); 590 bitmap->unlockPixels(); 591} 592 593/////////////////////////////////////////////////////////////////////////////// 594 595#include <android_runtime/AndroidRuntime.h> 596 597static JNINativeMethod gBitmapMethods[] = { 598 { "nativeCreate", "([IIIIIIZ)Landroid/graphics/Bitmap;", 599 (void*)Bitmap_creator }, 600 { "nativeCopy", "(IIZ)Landroid/graphics/Bitmap;", 601 (void*)Bitmap_copy }, 602 { "nativeDestructor", "(I)V", (void*)Bitmap_destructor }, 603 { "nativeRecycle", "(I)V", (void*)Bitmap_recycle }, 604 { "nativeCompress", "(IIILjava/io/OutputStream;[B)Z", 605 (void*)Bitmap_compress }, 606 { "nativeErase", "(II)V", (void*)Bitmap_erase }, 607 { "nativeWidth", "(I)I", (void*)Bitmap_width }, 608 { "nativeHeight", "(I)I", (void*)Bitmap_height }, 609 { "nativeRowBytes", "(I)I", (void*)Bitmap_rowBytes }, 610 { "nativeConfig", "(I)I", (void*)Bitmap_config }, 611 { "nativeHasAlpha", "(I)Z", (void*)Bitmap_hasAlpha }, 612 { "nativeSetHasAlpha", "(IZ)V", (void*)Bitmap_setHasAlpha }, 613 { "nativeCreateFromParcel", 614 "(Landroid/os/Parcel;)Landroid/graphics/Bitmap;", 615 (void*)Bitmap_createFromParcel }, 616 { "nativeWriteToParcel", "(IZILandroid/os/Parcel;)Z", 617 (void*)Bitmap_writeToParcel }, 618 { "nativeExtractAlpha", "(II[I)Landroid/graphics/Bitmap;", 619 (void*)Bitmap_extractAlpha }, 620 { "nativeGenerationId", "(I)I", (void*)Bitmap_getGenerationId }, 621 { "nativeGetPixel", "(III)I", (void*)Bitmap_getPixel }, 622 { "nativeGetPixels", "(I[IIIIIII)V", (void*)Bitmap_getPixels }, 623 { "nativeSetPixel", "(IIII)V", (void*)Bitmap_setPixel }, 624 { "nativeSetPixels", "(I[IIIIIII)V", (void*)Bitmap_setPixels }, 625 { "nativeCopyPixelsToBuffer", "(ILjava/nio/Buffer;)V", 626 (void*)Bitmap_copyPixelsToBuffer }, 627 { "nativeCopyPixelsFromBuffer", "(ILjava/nio/Buffer;)V", 628 (void*)Bitmap_copyPixelsFromBuffer }, 629 { "nativeSameAs", "(II)Z", (void*)Bitmap_sameAs }, 630 { "nativePrepareToDraw", "(I)V", (void*)Bitmap_prepareToDraw }, 631}; 632 633#define kClassPathName "android/graphics/Bitmap" 634 635int register_android_graphics_Bitmap(JNIEnv* env); 636int register_android_graphics_Bitmap(JNIEnv* env) 637{ 638 return android::AndroidRuntime::registerNativeMethods(env, kClassPathName, 639 gBitmapMethods, SK_ARRAY_COUNT(gBitmapMethods)); 640} 641 642