Sat, 03 Jan 2015 20:18:00 +0100
Conditionally enable double key logic according to:
private browsing mode or privacy.thirdparty.isolate preference and
implement in GetCookieStringCommon and FindCookie where it counts...
With some reservations of how to convince FindCookie users to test
condition and pass a nullptr when disabling double key logic.
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 */
8 #include "SkPDFImage.h"
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"
21 static const int kNoColorTransform = 0;
23 static bool skip_compression(SkPDFCatalog* catalog) {
24 return SkToBool(catalog->getDocumentFlags() &
25 SkPDFDocument::kFavorSpeedOverSize_Flags);
26 }
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 }
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();
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 }
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();
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++;
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 }
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 }
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();
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 }
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();
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 }
189 static SkStream* create_black_image() {
190 SkStream* stream = SkNEW_ARGS(SkMemoryStream, (1));
191 ((uint8_t*)stream->getMemoryBase())[0] = 0;
192 return stream;
193 }
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;
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();
256 if (isTransparent != NULL) {
257 *isTransparent = transparent;
258 }
259 if (extractAlpha && (transparent || isOpaque)) {
260 SkSafeUnref(stream);
261 return NULL;
262 }
263 return stream;
264 }
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);
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 }
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 }
300 static uint16_t remove_alpha_argb4444(uint16_t pmColor) {
301 return SkPixel32ToPixel4444(
302 remove_alpha_argb8888(SkPixel4444ToPixel32(pmColor)));
303 }
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;
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 }
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 }
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;
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 }
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 }
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;
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();
434 outBitmap.setImmutable();
436 return outBitmap;
437 }
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 }
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 }
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 }
479 return image;
480 }
482 SkPDFImage::~SkPDFImage() {
483 fResources.unrefAll();
484 }
486 SkPDFImage* SkPDFImage::addSMask(SkPDFImage* mask) {
487 fResources.push(mask);
488 mask->ref();
489 insert("SMask", new SkPDFObjRef(mask))->unref();
490 return mask;
491 }
493 void SkPDFImage::getResources(const SkTSet<SkPDFObject*>& knownResourceObjects,
494 SkTSet<SkPDFObject*>* newResourceObjects) {
495 GetResourcesHelper(&fResources, knownResourceObjects, newResourceObjects);
496 }
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) {
507 if (bitmap.isImmutable()) {
508 fBitmap = bitmap;
509 } else {
510 bitmap.deepCopyTo(&fBitmap);
511 fBitmap.setImmutable();
512 }
514 if (stream != NULL) {
515 setData(stream);
516 fStreamValid = true;
517 } else {
518 fStreamValid = false;
519 }
521 SkBitmap::Config config = fBitmap.config();
523 insertName("Type", "XObject");
524 insertName("Subtype", "Image");
526 bool alphaOnly = (config == SkBitmap::kA8_Config);
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 }
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 }
549 int bitsPerComp = 8;
550 if (config == SkBitmap::kARGB_4444_Config) {
551 bitsPerComp = 4;
552 }
553 insertInt("BitsPerComponent", bitsPerComp);
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 }
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 }
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());
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 }