Bitmap.cpp revision 9aaa8269a3e7291aab84d01c3fc9c744d8f2d2f4
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    android::uirenderer::Caches::getInstance().textureCache.remove(bitmap);
258#endif
259    delete bitmap;
260}
261
262static void Bitmap_recycle(JNIEnv* env, jobject, SkBitmap* bitmap) {
263    bitmap->setPixels(NULL, NULL);
264}
265
266// These must match the int values in Bitmap.java
267enum JavaEncodeFormat {
268    kJPEG_JavaEncodeFormat = 0,
269    kPNG_JavaEncodeFormat = 1
270};
271
272static bool Bitmap_compress(JNIEnv* env, jobject clazz, SkBitmap* bitmap,
273                            int format, int quality,
274                            jobject jstream, jbyteArray jstorage) {
275    SkImageEncoder::Type fm;
276
277    switch (format) {
278    case kJPEG_JavaEncodeFormat:
279        fm = SkImageEncoder::kJPEG_Type;
280        break;
281    case kPNG_JavaEncodeFormat:
282        fm = SkImageEncoder::kPNG_Type;
283        break;
284    default:
285        return false;
286    }
287
288    bool success = false;
289    SkWStream* strm = CreateJavaOutputStreamAdaptor(env, jstream, jstorage);
290    if (NULL != strm) {
291        SkImageEncoder* encoder = SkImageEncoder::Create(fm);
292        if (NULL != encoder) {
293            success = encoder->encodeStream(strm, *bitmap, quality);
294            delete encoder;
295        }
296        delete strm;
297    }
298    return success;
299}
300
301static void Bitmap_erase(JNIEnv* env, jobject, SkBitmap* bitmap, jint color) {
302    bitmap->eraseColor(color);
303}
304
305static int Bitmap_width(JNIEnv* env, jobject, SkBitmap* bitmap) {
306    return bitmap->width();
307}
308
309static int Bitmap_height(JNIEnv* env, jobject, SkBitmap* bitmap) {
310    return bitmap->height();
311}
312
313static int Bitmap_rowBytes(JNIEnv* env, jobject, SkBitmap* bitmap) {
314    return bitmap->rowBytes();
315}
316
317static int Bitmap_config(JNIEnv* env, jobject, SkBitmap* bitmap) {
318    return bitmap->config();
319}
320
321static int Bitmap_getGenerationId(JNIEnv* env, jobject, SkBitmap* bitmap) {
322    return bitmap->getGenerationID();
323}
324
325static jboolean Bitmap_hasAlpha(JNIEnv* env, jobject, SkBitmap* bitmap) {
326    return !bitmap->isOpaque();
327}
328
329static void Bitmap_setHasAlpha(JNIEnv* env, jobject, SkBitmap* bitmap,
330                               jboolean hasAlpha) {
331    bitmap->setIsOpaque(!hasAlpha);
332}
333
334///////////////////////////////////////////////////////////////////////////////
335
336static jobject Bitmap_createFromParcel(JNIEnv* env, jobject, jobject parcel) {
337    if (parcel == NULL) {
338        SkDebugf("-------- unparcel parcel is NULL\n");
339        return NULL;
340    }
341
342    android::Parcel* p = android::parcelForJavaObject(env, parcel);
343
344    const bool              isMutable = p->readInt32() != 0;
345    const SkBitmap::Config  config = (SkBitmap::Config)p->readInt32();
346    const int               width = p->readInt32();
347    const int               height = p->readInt32();
348    const int               rowBytes = p->readInt32();
349    const int               density = p->readInt32();
350
351    if (SkBitmap::kARGB_8888_Config != config &&
352            SkBitmap::kRGB_565_Config != config &&
353            SkBitmap::kARGB_4444_Config != config &&
354            SkBitmap::kIndex8_Config != config &&
355            SkBitmap::kA8_Config != config) {
356        SkDebugf("Bitmap_createFromParcel unknown config: %d\n", config);
357        return NULL;
358    }
359
360    SkBitmap* bitmap = new SkBitmap;
361
362    bitmap->setConfig(config, width, height, rowBytes);
363
364    SkColorTable* ctable = NULL;
365    if (config == SkBitmap::kIndex8_Config) {
366        int count = p->readInt32();
367        if (count > 0) {
368            size_t size = count * sizeof(SkPMColor);
369            const SkPMColor* src = (const SkPMColor*)p->readInplace(size);
370            ctable = new SkColorTable(src, count);
371        }
372    }
373
374    if (!GraphicsJNI::setJavaPixelRef(env, bitmap, ctable, true)) {
375        ctable->safeUnref();
376        delete bitmap;
377        return NULL;
378    }
379
380    ctable->safeUnref();
381
382    size_t size = bitmap->getSize();
383    bitmap->lockPixels();
384    memcpy(bitmap->getPixels(), p->readInplace(size), size);
385    bitmap->unlockPixels();
386
387    return GraphicsJNI::createBitmap(env, bitmap, isMutable, NULL, density);
388}
389
390static jboolean Bitmap_writeToParcel(JNIEnv* env, jobject,
391                                     const SkBitmap* bitmap,
392                                     jboolean isMutable, jint density,
393                                     jobject parcel) {
394    if (parcel == NULL) {
395        SkDebugf("------- writeToParcel null parcel\n");
396        return false;
397    }
398
399    android::Parcel* p = android::parcelForJavaObject(env, parcel);
400
401    p->writeInt32(isMutable);
402    p->writeInt32(bitmap->config());
403    p->writeInt32(bitmap->width());
404    p->writeInt32(bitmap->height());
405    p->writeInt32(bitmap->rowBytes());
406    p->writeInt32(density);
407
408    if (bitmap->getConfig() == SkBitmap::kIndex8_Config) {
409        SkColorTable* ctable = bitmap->getColorTable();
410        if (ctable != NULL) {
411            int count = ctable->count();
412            p->writeInt32(count);
413            memcpy(p->writeInplace(count * sizeof(SkPMColor)),
414                   ctable->lockColors(), count * sizeof(SkPMColor));
415            ctable->unlockColors(false);
416        } else {
417            p->writeInt32(0);   // indicate no ctable
418        }
419    }
420
421    size_t size = bitmap->getSize();
422    bitmap->lockPixels();
423    memcpy(p->writeInplace(size), bitmap->getPixels(), size);
424    bitmap->unlockPixels();
425    return true;
426}
427
428static jobject Bitmap_extractAlpha(JNIEnv* env, jobject clazz,
429                                   const SkBitmap* src, const SkPaint* paint,
430                                   jintArray offsetXY) {
431    SkIPoint  offset;
432    SkBitmap* dst = new SkBitmap;
433
434    src->extractAlpha(dst, paint, &offset);
435    if (offsetXY != 0 && env->GetArrayLength(offsetXY) >= 2) {
436        int* array = env->GetIntArrayElements(offsetXY, NULL);
437        array[0] = offset.fX;
438        array[1] = offset.fY;
439        env->ReleaseIntArrayElements(offsetXY, array, 0);
440    }
441
442    return GraphicsJNI::createBitmap(env, dst, true, NULL);
443}
444
445///////////////////////////////////////////////////////////////////////////////
446
447static int Bitmap_getPixel(JNIEnv* env, jobject, const SkBitmap* bitmap,
448                           int x, int y) {
449    SkAutoLockPixels alp(*bitmap);
450
451    ToColorProc proc = ChooseToColorProc(*bitmap);
452    if (NULL == proc) {
453        return 0;
454    }
455    const void* src = bitmap->getAddr(x, y);
456    if (NULL == src) {
457        return 0;
458    }
459
460    SkColor dst[1];
461    proc(dst, src, 1, bitmap->getColorTable());
462    return dst[0];
463}
464
465static void Bitmap_getPixels(JNIEnv* env, jobject, const SkBitmap* bitmap,
466                             jintArray pixelArray, int offset, int stride,
467                             int x, int y, int width, int height) {
468    SkAutoLockPixels alp(*bitmap);
469
470    ToColorProc proc = ChooseToColorProc(*bitmap);
471    if (NULL == proc) {
472        return;
473    }
474    const void* src = bitmap->getAddr(x, y);
475    if (NULL == src) {
476        return;
477    }
478
479    SkColorTable* ctable = bitmap->getColorTable();
480    jint* dst = env->GetIntArrayElements(pixelArray, NULL);
481    SkColor* d = (SkColor*)dst + offset;
482    while (--height >= 0) {
483        proc(d, src, width, ctable);
484        d += stride;
485        src = (void*)((const char*)src + bitmap->rowBytes());
486    }
487    env->ReleaseIntArrayElements(pixelArray, dst, 0);
488}
489
490///////////////////////////////////////////////////////////////////////////////
491
492static void Bitmap_setPixel(JNIEnv* env, jobject, const SkBitmap* bitmap,
493                            int x, int y, SkColor color) {
494    SkAutoLockPixels alp(*bitmap);
495    if (NULL == bitmap->getPixels()) {
496        return;
497    }
498
499    FromColorProc proc = ChooseFromColorProc(bitmap->config());
500    if (NULL == proc) {
501        return;
502    }
503
504    proc(bitmap->getAddr(x, y), &color, 1, x, y);
505}
506
507static void Bitmap_setPixels(JNIEnv* env, jobject, const SkBitmap* bitmap,
508                             jintArray pixelArray, int offset, int stride,
509                             int x, int y, int width, int height) {
510    GraphicsJNI::SetPixels(env, pixelArray, offset, stride,
511                           x, y, width, height, *bitmap);
512}
513
514static void Bitmap_copyPixelsToBuffer(JNIEnv* env, jobject,
515                                      const SkBitmap* bitmap, jobject jbuffer) {
516    SkAutoLockPixels alp(*bitmap);
517    const void* src = bitmap->getPixels();
518
519    if (NULL != src) {
520        android::AutoBufferPointer abp(env, jbuffer, JNI_TRUE);
521
522        // the java side has already checked that buffer is large enough
523        memcpy(abp.pointer(), src, bitmap->getSize());
524    }
525}
526
527static void Bitmap_copyPixelsFromBuffer(JNIEnv* env, jobject,
528                                    const SkBitmap* bitmap, jobject jbuffer) {
529    SkAutoLockPixels alp(*bitmap);
530    void* dst = bitmap->getPixels();
531
532    if (NULL != dst) {
533        android::AutoBufferPointer abp(env, jbuffer, JNI_FALSE);
534        // the java side has already checked that buffer is large enough
535        memcpy(dst, abp.pointer(), bitmap->getSize());
536    }
537}
538
539static bool Bitmap_sameAs(JNIEnv* env, jobject, const SkBitmap* bm0,
540                             const SkBitmap* bm1) {
541    if (bm0->width() != bm1->width() ||
542        bm0->height() != bm1->height() ||
543        bm0->config() != bm1->config()) {
544        return false;
545    }
546
547    SkAutoLockPixels alp0(*bm0);
548    SkAutoLockPixels alp1(*bm1);
549
550    // if we can't load the pixels, return false
551    if (NULL == bm0->getPixels() || NULL == bm1->getPixels()) {
552        return false;
553    }
554
555    if (bm0->config() == SkBitmap::kIndex8_Config) {
556        SkColorTable* ct0 = bm0->getColorTable();
557        SkColorTable* ct1 = bm1->getColorTable();
558        if (NULL == ct0 || NULL == ct1) {
559            return false;
560        }
561        if (ct0->count() != ct1->count()) {
562            return false;
563        }
564
565        SkAutoLockColors alc0(ct0);
566        SkAutoLockColors alc1(ct1);
567        const size_t size = ct0->count() * sizeof(SkPMColor);
568        if (memcmp(alc0.colors(), alc1.colors(), size) != 0) {
569            return false;
570        }
571    }
572
573    // now compare each scanline. We can't do the entire buffer at once,
574    // since we don't care about the pixel values that might extend beyond
575    // the width (since the scanline might be larger than the logical width)
576    const int h = bm0->height();
577    const size_t size = bm0->width() * bm0->bytesPerPixel();
578    for (int y = 0; y < h; y++) {
579        if (memcmp(bm0->getAddr(0, y), bm1->getAddr(0, y), size) != 0) {
580            return false;
581        }
582    }
583    return true;
584}
585
586static void Bitmap_prepareToDraw(JNIEnv* env, jobject, SkBitmap* bitmap) {
587    bitmap->lockPixels();
588    bitmap->unlockPixels();
589}
590
591///////////////////////////////////////////////////////////////////////////////
592
593#include <android_runtime/AndroidRuntime.h>
594
595static JNINativeMethod gBitmapMethods[] = {
596    {   "nativeCreate",             "([IIIIIIZ)Landroid/graphics/Bitmap;",
597        (void*)Bitmap_creator },
598    {   "nativeCopy",               "(IIZ)Landroid/graphics/Bitmap;",
599        (void*)Bitmap_copy },
600    {   "nativeDestructor",         "(I)V", (void*)Bitmap_destructor },
601    {   "nativeRecycle",            "(I)V", (void*)Bitmap_recycle },
602    {   "nativeCompress",           "(IIILjava/io/OutputStream;[B)Z",
603        (void*)Bitmap_compress },
604    {   "nativeErase",              "(II)V", (void*)Bitmap_erase },
605    {   "nativeWidth",              "(I)I", (void*)Bitmap_width },
606    {   "nativeHeight",             "(I)I", (void*)Bitmap_height },
607    {   "nativeRowBytes",           "(I)I", (void*)Bitmap_rowBytes },
608    {   "nativeConfig",             "(I)I", (void*)Bitmap_config },
609    {   "nativeHasAlpha",           "(I)Z", (void*)Bitmap_hasAlpha },
610    {   "nativeSetHasAlpha",        "(IZ)V", (void*)Bitmap_setHasAlpha },
611    {   "nativeCreateFromParcel",
612        "(Landroid/os/Parcel;)Landroid/graphics/Bitmap;",
613        (void*)Bitmap_createFromParcel },
614    {   "nativeWriteToParcel",      "(IZILandroid/os/Parcel;)Z",
615        (void*)Bitmap_writeToParcel },
616    {   "nativeExtractAlpha",       "(II[I)Landroid/graphics/Bitmap;",
617        (void*)Bitmap_extractAlpha },
618    {   "nativeGenerationId",       "(I)I", (void*)Bitmap_getGenerationId },
619    {   "nativeGetPixel",           "(III)I", (void*)Bitmap_getPixel },
620    {   "nativeGetPixels",          "(I[IIIIIII)V", (void*)Bitmap_getPixels },
621    {   "nativeSetPixel",           "(IIII)V", (void*)Bitmap_setPixel },
622    {   "nativeSetPixels",          "(I[IIIIIII)V", (void*)Bitmap_setPixels },
623    {   "nativeCopyPixelsToBuffer", "(ILjava/nio/Buffer;)V",
624                                            (void*)Bitmap_copyPixelsToBuffer },
625    {   "nativeCopyPixelsFromBuffer", "(ILjava/nio/Buffer;)V",
626                                            (void*)Bitmap_copyPixelsFromBuffer },
627    {   "nativeSameAs",             "(II)Z", (void*)Bitmap_sameAs },
628    {   "nativePrepareToDraw",      "(I)V", (void*)Bitmap_prepareToDraw },
629};
630
631#define kClassPathName  "android/graphics/Bitmap"
632
633int register_android_graphics_Bitmap(JNIEnv* env);
634int register_android_graphics_Bitmap(JNIEnv* env)
635{
636    return android::AndroidRuntime::registerNativeMethods(env, kClassPathName,
637                                gBitmapMethods, SK_ARRAY_COUNT(gBitmapMethods));
638}
639
640