1 #define LOG_TAG "BitmapFactory"
2
3 #include "BitmapFactory.h"
4 #include "NinePatchPeeker.h"
5 #include "SkImageDecoder.h"
6 #include "SkImageRef_ashmem.h"
7 #include "SkImageRef_GlobalPool.h"
8 #include "SkPixelRef.h"
9 #include "SkStream.h"
10 #include "SkTemplates.h"
11 #include "SkUtils.h"
12 #include "CreateJavaOutputStreamAdaptor.h"
13 #include "AutoDecodeCancel.h"
14 #include "Utils.h"
15 #include "JNIHelp.h"
16
17 #include <android_runtime/AndroidRuntime.h>
18 #include <utils/Asset.h>
19 #include <utils/ResourceTypes.h>
20 #include <netinet/in.h>
21 #include <sys/mman.h>
22 #include <sys/stat.h>
23
24 jfieldID gOptions_justBoundsFieldID;
25 jfieldID gOptions_sampleSizeFieldID;
26 jfieldID gOptions_configFieldID;
27 jfieldID gOptions_mutableFieldID;
28 jfieldID gOptions_ditherFieldID;
29 jfieldID gOptions_purgeableFieldID;
30 jfieldID gOptions_shareableFieldID;
31 jfieldID gOptions_preferQualityOverSpeedFieldID;
32 jfieldID gOptions_widthFieldID;
33 jfieldID gOptions_heightFieldID;
34 jfieldID gOptions_mimeFieldID;
35 jfieldID gOptions_mCancelID;
36 jfieldID gOptions_bitmapFieldID;
37 jfieldID gBitmap_nativeBitmapFieldID;
38
39 #if 0
40 #define TRACE_BITMAP(code) code
41 #else
42 #define TRACE_BITMAP(code)
43 #endif
44
45 using namespace android;
46
validOrNeg1(bool isValid,int32_t value)47 static inline int32_t validOrNeg1(bool isValid, int32_t value) {
48 // return isValid ? value : -1;
49 SkASSERT((int)isValid == 0 || (int)isValid == 1);
50 return ((int32_t)isValid - 1) | value;
51 }
52
getMimeTypeString(JNIEnv * env,SkImageDecoder::Format format)53 jstring getMimeTypeString(JNIEnv* env, SkImageDecoder::Format format) {
54 static const struct {
55 SkImageDecoder::Format fFormat;
56 const char* fMimeType;
57 } gMimeTypes[] = {
58 { SkImageDecoder::kBMP_Format, "image/bmp" },
59 { SkImageDecoder::kGIF_Format, "image/gif" },
60 { SkImageDecoder::kICO_Format, "image/x-ico" },
61 { SkImageDecoder::kJPEG_Format, "image/jpeg" },
62 { SkImageDecoder::kPNG_Format, "image/png" },
63 { SkImageDecoder::kWBMP_Format, "image/vnd.wap.wbmp" }
64 };
65
66 const char* cstr = NULL;
67 for (size_t i = 0; i < SK_ARRAY_COUNT(gMimeTypes); i++) {
68 if (gMimeTypes[i].fFormat == format) {
69 cstr = gMimeTypes[i].fMimeType;
70 break;
71 }
72 }
73
74 jstring jstr = 0;
75 if (NULL != cstr) {
76 jstr = env->NewStringUTF(cstr);
77 }
78 return jstr;
79 }
80
optionsPurgeable(JNIEnv * env,jobject options)81 static bool optionsPurgeable(JNIEnv* env, jobject options) {
82 return options != NULL &&
83 env->GetBooleanField(options, gOptions_purgeableFieldID);
84 }
85
optionsShareable(JNIEnv * env,jobject options)86 static bool optionsShareable(JNIEnv* env, jobject options) {
87 return options != NULL &&
88 env->GetBooleanField(options, gOptions_shareableFieldID);
89 }
90
optionsJustBounds(JNIEnv * env,jobject options)91 static bool optionsJustBounds(JNIEnv* env, jobject options) {
92 return options != NULL &&
93 env->GetBooleanField(options, gOptions_justBoundsFieldID);
94 }
95
installPixelRef(SkBitmap * bitmap,SkStream * stream,int sampleSize,bool ditherImage)96 static SkPixelRef* installPixelRef(SkBitmap* bitmap, SkStream* stream,
97 int sampleSize, bool ditherImage) {
98 SkImageRef* pr;
99 // only use ashmem for large images, since mmaps come at a price
100 if (bitmap->getSize() >= 32 * 1024) {
101 pr = new SkImageRef_ashmem(stream, bitmap->config(), sampleSize);
102 } else {
103 pr = new SkImageRef_GlobalPool(stream, bitmap->config(), sampleSize);
104 }
105 pr->setDitherImage(ditherImage);
106 bitmap->setPixelRef(pr)->unref();
107 pr->isOpaque(bitmap);
108 return pr;
109 }
110
111 // since we "may" create a purgeable imageref, we require the stream be ref'able
112 // i.e. dynamically allocated, since its lifetime may exceed the current stack
113 // frame.
doDecode(JNIEnv * env,SkStream * stream,jobject padding,jobject options,bool allowPurgeable,bool forcePurgeable=false)114 static jobject doDecode(JNIEnv* env, SkStream* stream, jobject padding,
115 jobject options, bool allowPurgeable,
116 bool forcePurgeable = false) {
117 int sampleSize = 1;
118 SkImageDecoder::Mode mode = SkImageDecoder::kDecodePixels_Mode;
119 SkBitmap::Config prefConfig = SkBitmap::kARGB_8888_Config;
120 bool doDither = true;
121 bool isMutable = false;
122 bool isPurgeable = forcePurgeable ||
123 (allowPurgeable && optionsPurgeable(env, options));
124 bool preferQualityOverSpeed = false;
125 jobject javaBitmap = NULL;
126
127 if (NULL != options) {
128 sampleSize = env->GetIntField(options, gOptions_sampleSizeFieldID);
129 if (optionsJustBounds(env, options)) {
130 mode = SkImageDecoder::kDecodeBounds_Mode;
131 }
132 // initialize these, in case we fail later on
133 env->SetIntField(options, gOptions_widthFieldID, -1);
134 env->SetIntField(options, gOptions_heightFieldID, -1);
135 env->SetObjectField(options, gOptions_mimeFieldID, 0);
136
137 jobject jconfig = env->GetObjectField(options, gOptions_configFieldID);
138 prefConfig = GraphicsJNI::getNativeBitmapConfig(env, jconfig);
139 isMutable = env->GetBooleanField(options, gOptions_mutableFieldID);
140 doDither = env->GetBooleanField(options, gOptions_ditherFieldID);
141 preferQualityOverSpeed = env->GetBooleanField(options,
142 gOptions_preferQualityOverSpeedFieldID);
143 javaBitmap = env->GetObjectField(options, gOptions_bitmapFieldID);
144 }
145
146 SkImageDecoder* decoder = SkImageDecoder::Factory(stream);
147 if (NULL == decoder) {
148 return nullObjectReturn("SkImageDecoder::Factory returned null");
149 }
150
151 decoder->setSampleSize(sampleSize);
152 decoder->setDitherImage(doDither);
153 decoder->setPreferQualityOverSpeed(preferQualityOverSpeed);
154
155 NinePatchPeeker peeker(decoder);
156 JavaPixelAllocator javaAllocator(env);
157 SkBitmap* bitmap;
158 if (javaBitmap == NULL) {
159 bitmap = new SkBitmap;
160 } else {
161 if (sampleSize != 1) {
162 return nullObjectReturn("SkImageDecoder: Cannot reuse bitmap with sampleSize != 1");
163 }
164 bitmap = (SkBitmap *) env->GetIntField(javaBitmap, gBitmap_nativeBitmapFieldID);
165 // config of supplied bitmap overrules config set in options
166 prefConfig = bitmap->getConfig();
167 }
168 Res_png_9patch dummy9Patch;
169
170 SkAutoTDelete<SkImageDecoder> add(decoder);
171 SkAutoTDelete<SkBitmap> adb(bitmap, (javaBitmap == NULL));
172
173 decoder->setPeeker(&peeker);
174 if (!isPurgeable) {
175 decoder->setAllocator(&javaAllocator);
176 }
177
178 AutoDecoderCancel adc(options, decoder);
179
180 // To fix the race condition in case "requestCancelDecode"
181 // happens earlier than AutoDecoderCancel object is added
182 // to the gAutoDecoderCancelMutex linked list.
183 if (NULL != options && env->GetBooleanField(options, gOptions_mCancelID)) {
184 return nullObjectReturn("gOptions_mCancelID");
185 }
186
187 SkImageDecoder::Mode decodeMode = mode;
188 if (isPurgeable) {
189 decodeMode = SkImageDecoder::kDecodeBounds_Mode;
190 }
191 if (!decoder->decode(stream, bitmap, prefConfig, decodeMode, javaBitmap != NULL)) {
192 return nullObjectReturn("decoder->decode returned false");
193 }
194
195 // update options (if any)
196 if (NULL != options) {
197 env->SetIntField(options, gOptions_widthFieldID, bitmap->width());
198 env->SetIntField(options, gOptions_heightFieldID, bitmap->height());
199 // TODO: set the mimeType field with the data from the codec.
200 // but how to reuse a set of strings, rather than allocating new one
201 // each time?
202 env->SetObjectField(options, gOptions_mimeFieldID,
203 getMimeTypeString(env, decoder->getFormat()));
204 }
205
206 // if we're in justBounds mode, return now (skip the java bitmap)
207 if (SkImageDecoder::kDecodeBounds_Mode == mode) {
208 return NULL;
209 }
210
211 jbyteArray ninePatchChunk = NULL;
212 if (peeker.fPatchIsValid) {
213 size_t ninePatchArraySize = peeker.fPatch->serializedSize();
214 ninePatchChunk = env->NewByteArray(ninePatchArraySize);
215 if (NULL == ninePatchChunk) {
216 return nullObjectReturn("ninePatchChunk == null");
217 }
218 jbyte* array = (jbyte*)env->GetPrimitiveArrayCritical(ninePatchChunk,
219 NULL);
220 if (NULL == array) {
221 return nullObjectReturn("primitive array == null");
222 }
223 peeker.fPatch->serialize(array);
224 env->ReleasePrimitiveArrayCritical(ninePatchChunk, array, 0);
225 }
226
227 // detach bitmap from its autodeleter, since we want to own it now
228 adb.detach();
229
230 if (padding) {
231 if (peeker.fPatchIsValid) {
232 GraphicsJNI::set_jrect(env, padding,
233 peeker.fPatch->paddingLeft,
234 peeker.fPatch->paddingTop,
235 peeker.fPatch->paddingRight,
236 peeker.fPatch->paddingBottom);
237 } else {
238 GraphicsJNI::set_jrect(env, padding, -1, -1, -1, -1);
239 }
240 }
241
242 SkPixelRef* pr;
243 if (isPurgeable) {
244 pr = installPixelRef(bitmap, stream, sampleSize, doDither);
245 } else {
246 // if we get here, we're in kDecodePixels_Mode and will therefore
247 // already have a pixelref installed.
248 pr = bitmap->pixelRef();
249 }
250
251 if (!isMutable) {
252 // promise we will never change our pixels (great for sharing and pictures)
253 pr->setImmutable();
254 }
255
256 if (javaBitmap != NULL) {
257 // If a java bitmap was passed in for reuse, pass it back
258 return javaBitmap;
259 }
260 // now create the java bitmap
261 return GraphicsJNI::createBitmap(env, bitmap, javaAllocator.getStorageObj(),
262 isMutable, ninePatchChunk);
263 }
264
nativeDecodeStream(JNIEnv * env,jobject clazz,jobject is,jbyteArray storage,jobject padding,jobject options)265 static jobject nativeDecodeStream(JNIEnv* env, jobject clazz,
266 jobject is, // InputStream
267 jbyteArray storage, // byte[]
268 jobject padding,
269 jobject options) { // BitmapFactory$Options
270 jobject bitmap = NULL;
271 SkStream* stream = CreateJavaInputStreamAdaptor(env, is, storage, 0);
272
273 if (stream) {
274 // for now we don't allow purgeable with java inputstreams
275 bitmap = doDecode(env, stream, padding, options, false);
276 stream->unref();
277 }
278 return bitmap;
279 }
280
getFDSize(int fd)281 static ssize_t getFDSize(int fd) {
282 off64_t curr = ::lseek64(fd, 0, SEEK_CUR);
283 if (curr < 0) {
284 return 0;
285 }
286 size_t size = ::lseek(fd, 0, SEEK_END);
287 ::lseek64(fd, curr, SEEK_SET);
288 return size;
289 }
290
nativeDecodeFileDescriptor(JNIEnv * env,jobject clazz,jobject fileDescriptor,jobject padding,jobject bitmapFactoryOptions)291 static jobject nativeDecodeFileDescriptor(JNIEnv* env, jobject clazz,
292 jobject fileDescriptor,
293 jobject padding,
294 jobject bitmapFactoryOptions) {
295 NPE_CHECK_RETURN_ZERO(env, fileDescriptor);
296
297 jint descriptor = jniGetFDFromFileDescriptor(env, fileDescriptor);
298
299 bool isPurgeable = optionsPurgeable(env, bitmapFactoryOptions);
300 bool isShareable = optionsShareable(env, bitmapFactoryOptions);
301 bool weOwnTheFD = false;
302 if (isPurgeable && isShareable) {
303 int newFD = ::dup(descriptor);
304 if (-1 != newFD) {
305 weOwnTheFD = true;
306 descriptor = newFD;
307 }
308 }
309
310 SkFDStream* stream = new SkFDStream(descriptor, weOwnTheFD);
311 SkAutoUnref aur(stream);
312 if (!stream->isValid()) {
313 return NULL;
314 }
315
316 /* Restore our offset when we leave, so we can be called more than once
317 with the same descriptor. This is only required if we didn't dup the
318 file descriptor, but it is OK to do it all the time.
319 */
320 AutoFDSeek as(descriptor);
321
322 /* Allow purgeable iff we own the FD, i.e., in the puregeable and
323 shareable case.
324 */
325 return doDecode(env, stream, padding, bitmapFactoryOptions, weOwnTheFD);
326 }
327
328 /* make a deep copy of the asset, and return it as a stream, or NULL if there
329 was an error.
330 */
copyAssetToStream(Asset * asset)331 static SkStream* copyAssetToStream(Asset* asset) {
332 // if we could "ref/reopen" the asset, we may not need to copy it here
333 off64_t size = asset->seek(0, SEEK_SET);
334 if ((off64_t)-1 == size) {
335 SkDebugf("---- copyAsset: asset rewind failed\n");
336 return NULL;
337 }
338
339 size = asset->getLength();
340 if (size <= 0) {
341 SkDebugf("---- copyAsset: asset->getLength() returned %d\n", size);
342 return NULL;
343 }
344
345 SkStream* stream = new SkMemoryStream(size);
346 void* data = const_cast<void*>(stream->getMemoryBase());
347 off64_t len = asset->read(data, size);
348 if (len != size) {
349 SkDebugf("---- copyAsset: asset->read(%d) returned %d\n", size, len);
350 delete stream;
351 stream = NULL;
352 }
353 return stream;
354 }
355
nativeDecodeAsset(JNIEnv * env,jobject clazz,jint native_asset,jobject padding,jobject options)356 static jobject nativeDecodeAsset(JNIEnv* env, jobject clazz,
357 jint native_asset, // Asset
358 jobject padding, // Rect
359 jobject options) { // BitmapFactory$Options
360 SkStream* stream;
361 Asset* asset = reinterpret_cast<Asset*>(native_asset);
362 bool forcePurgeable = optionsPurgeable(env, options);
363 if (forcePurgeable) {
364 // if we could "ref/reopen" the asset, we may not need to copy it here
365 // and we could assume optionsShareable, since assets are always RO
366 stream = copyAssetToStream(asset);
367 if (NULL == stream) {
368 return NULL;
369 }
370 } else {
371 // since we know we'll be done with the asset when we return, we can
372 // just use a simple wrapper
373 stream = new AssetStreamAdaptor(asset);
374 }
375 SkAutoUnref aur(stream);
376 return doDecode(env, stream, padding, options, true, forcePurgeable);
377 }
378
nativeDecodeByteArray(JNIEnv * env,jobject,jbyteArray byteArray,int offset,int length,jobject options)379 static jobject nativeDecodeByteArray(JNIEnv* env, jobject, jbyteArray byteArray,
380 int offset, int length, jobject options) {
381 /* If optionsShareable() we could decide to just wrap the java array and
382 share it, but that means adding a globalref to the java array object
383 and managing its lifetime. For now we just always copy the array's data
384 if optionsPurgeable(), unless we're just decoding bounds.
385 */
386 bool purgeable = optionsPurgeable(env, options)
387 && !optionsJustBounds(env, options);
388 AutoJavaByteArray ar(env, byteArray);
389 SkStream* stream = new SkMemoryStream(ar.ptr() + offset, length, purgeable);
390 SkAutoUnref aur(stream);
391 return doDecode(env, stream, NULL, options, purgeable);
392 }
393
nativeRequestCancel(JNIEnv *,jobject joptions)394 static void nativeRequestCancel(JNIEnv*, jobject joptions) {
395 (void)AutoDecoderCancel::RequestCancel(joptions);
396 }
397
nativeScaleNinePatch(JNIEnv * env,jobject,jbyteArray chunkObject,jfloat scale,jobject padding)398 static jbyteArray nativeScaleNinePatch(JNIEnv* env, jobject, jbyteArray chunkObject, jfloat scale,
399 jobject padding) {
400
401 jbyte* array = env->GetByteArrayElements(chunkObject, 0);
402 if (array != NULL) {
403 size_t chunkSize = env->GetArrayLength(chunkObject);
404 void* storage = alloca(chunkSize);
405 android::Res_png_9patch* chunk = static_cast<android::Res_png_9patch*>(storage);
406 memcpy(chunk, array, chunkSize);
407 android::Res_png_9patch::deserialize(chunk);
408
409 chunk->paddingLeft = int(chunk->paddingLeft * scale + 0.5f);
410 chunk->paddingTop = int(chunk->paddingTop * scale + 0.5f);
411 chunk->paddingRight = int(chunk->paddingRight * scale + 0.5f);
412 chunk->paddingBottom = int(chunk->paddingBottom * scale + 0.5f);
413
414 for (int i = 0; i < chunk->numXDivs; i++) {
415 chunk->xDivs[i] = int(chunk->xDivs[i] * scale + 0.5f);
416 if (i > 0 && chunk->xDivs[i] == chunk->xDivs[i - 1]) {
417 chunk->xDivs[i]++;
418 }
419 }
420
421 for (int i = 0; i < chunk->numYDivs; i++) {
422 chunk->yDivs[i] = int(chunk->yDivs[i] * scale + 0.5f);
423 if (i > 0 && chunk->yDivs[i] == chunk->yDivs[i - 1]) {
424 chunk->yDivs[i]++;
425 }
426 }
427
428 memcpy(array, chunk, chunkSize);
429
430 if (padding) {
431 GraphicsJNI::set_jrect(env, padding, chunk->paddingLeft, chunk->paddingTop,
432 chunk->paddingRight, chunk->paddingBottom);
433 }
434
435 env->ReleaseByteArrayElements(chunkObject, array, 0);
436 }
437 return chunkObject;
438 }
439
nativeSetDefaultConfig(JNIEnv * env,jobject,int nativeConfig)440 static void nativeSetDefaultConfig(JNIEnv* env, jobject, int nativeConfig) {
441 SkBitmap::Config config = static_cast<SkBitmap::Config>(nativeConfig);
442
443 // these are the only default configs that make sense for codecs right now
444 static const SkBitmap::Config gValidDefConfig[] = {
445 SkBitmap::kRGB_565_Config,
446 SkBitmap::kARGB_8888_Config,
447 };
448
449 for (size_t i = 0; i < SK_ARRAY_COUNT(gValidDefConfig); i++) {
450 if (config == gValidDefConfig[i]) {
451 SkImageDecoder::SetDeviceConfig(config);
452 break;
453 }
454 }
455 }
456
nativeIsSeekable(JNIEnv * env,jobject,jobject fileDescriptor)457 static jboolean nativeIsSeekable(JNIEnv* env, jobject, jobject fileDescriptor) {
458 jint descriptor = jniGetFDFromFileDescriptor(env, fileDescriptor);
459 return ::lseek64(descriptor, 0, SEEK_CUR) != -1 ? JNI_TRUE : JNI_FALSE;
460 }
461
462 ///////////////////////////////////////////////////////////////////////////////
463
464 static JNINativeMethod gMethods[] = {
465 { "nativeDecodeStream",
466 "(Ljava/io/InputStream;[BLandroid/graphics/Rect;Landroid/graphics/BitmapFactory$Options;)Landroid/graphics/Bitmap;",
467 (void*)nativeDecodeStream
468 },
469
470 { "nativeDecodeFileDescriptor",
471 "(Ljava/io/FileDescriptor;Landroid/graphics/Rect;Landroid/graphics/BitmapFactory$Options;)Landroid/graphics/Bitmap;",
472 (void*)nativeDecodeFileDescriptor
473 },
474
475 { "nativeDecodeAsset",
476 "(ILandroid/graphics/Rect;Landroid/graphics/BitmapFactory$Options;)Landroid/graphics/Bitmap;",
477 (void*)nativeDecodeAsset
478 },
479
480 { "nativeDecodeByteArray",
481 "([BIILandroid/graphics/BitmapFactory$Options;)Landroid/graphics/Bitmap;",
482 (void*)nativeDecodeByteArray
483 },
484
485 { "nativeScaleNinePatch",
486 "([BFLandroid/graphics/Rect;)[B",
487 (void*)nativeScaleNinePatch
488 },
489
490 { "nativeSetDefaultConfig", "(I)V", (void*)nativeSetDefaultConfig },
491
492 { "nativeIsSeekable",
493 "(Ljava/io/FileDescriptor;)Z",
494 (void*)nativeIsSeekable
495 },
496 };
497
498 static JNINativeMethod gOptionsMethods[] = {
499 { "requestCancel", "()V", (void*)nativeRequestCancel }
500 };
501
getFieldIDCheck(JNIEnv * env,jclass clazz,const char fieldname[],const char type[])502 static jfieldID getFieldIDCheck(JNIEnv* env, jclass clazz,
503 const char fieldname[], const char type[]) {
504 jfieldID id = env->GetFieldID(clazz, fieldname, type);
505 SkASSERT(id);
506 return id;
507 }
508
register_android_graphics_BitmapFactory(JNIEnv * env)509 int register_android_graphics_BitmapFactory(JNIEnv* env) {
510 jclass options_class = env->FindClass("android/graphics/BitmapFactory$Options");
511 SkASSERT(options_class);
512 gOptions_bitmapFieldID = getFieldIDCheck(env, options_class, "inBitmap",
513 "Landroid/graphics/Bitmap;");
514 gOptions_justBoundsFieldID = getFieldIDCheck(env, options_class, "inJustDecodeBounds", "Z");
515 gOptions_sampleSizeFieldID = getFieldIDCheck(env, options_class, "inSampleSize", "I");
516 gOptions_configFieldID = getFieldIDCheck(env, options_class, "inPreferredConfig",
517 "Landroid/graphics/Bitmap$Config;");
518 gOptions_mutableFieldID = getFieldIDCheck(env, options_class, "inMutable", "Z");
519 gOptions_ditherFieldID = getFieldIDCheck(env, options_class, "inDither", "Z");
520 gOptions_purgeableFieldID = getFieldIDCheck(env, options_class, "inPurgeable", "Z");
521 gOptions_shareableFieldID = getFieldIDCheck(env, options_class, "inInputShareable", "Z");
522 gOptions_preferQualityOverSpeedFieldID = getFieldIDCheck(env, options_class,
523 "inPreferQualityOverSpeed", "Z");
524 gOptions_widthFieldID = getFieldIDCheck(env, options_class, "outWidth", "I");
525 gOptions_heightFieldID = getFieldIDCheck(env, options_class, "outHeight", "I");
526 gOptions_mimeFieldID = getFieldIDCheck(env, options_class, "outMimeType", "Ljava/lang/String;");
527 gOptions_mCancelID = getFieldIDCheck(env, options_class, "mCancel", "Z");
528
529 jclass bitmap_class = env->FindClass("android/graphics/Bitmap");
530 SkASSERT(bitmap_class);
531 gBitmap_nativeBitmapFieldID = getFieldIDCheck(env, bitmap_class, "mNativeBitmap", "I");
532
533 int ret = AndroidRuntime::registerNativeMethods(env,
534 "android/graphics/BitmapFactory$Options",
535 gOptionsMethods,
536 SK_ARRAY_COUNT(gOptionsMethods));
537 if (ret) {
538 return ret;
539 }
540 return android::AndroidRuntime::registerNativeMethods(env, "android/graphics/BitmapFactory",
541 gMethods, SK_ARRAY_COUNT(gMethods));
542 }
543