1 | /** |
2 | * Copyright (c) 2006-2023 LOVE Development Team |
3 | * |
4 | * This software is provided 'as-is', without any express or implied |
5 | * warranty. In no event will the authors be held liable for any damages |
6 | * arising from the use of this software. |
7 | * |
8 | * Permission is granted to anyone to use this software for any purpose, |
9 | * including commercial applications, and to alter it and redistribute it |
10 | * freely, subject to the following restrictions: |
11 | * |
12 | * 1. The origin of this software must not be misrepresented; you must not |
13 | * claim that you wrote the original software. If you use this software |
14 | * in a product, an acknowledgment in the product documentation would be |
15 | * appreciated but is not required. |
16 | * 2. Altered source versions must be plainly marked as such, and must not be |
17 | * misrepresented as being the original software. |
18 | * 3. This notice may not be removed or altered from any source distribution. |
19 | **/ |
20 | |
21 | // LOVE |
22 | #include "PVRHandler.h" |
23 | #include "common/int.h" |
24 | #include "common/Exception.h" |
25 | |
26 | // C++ |
27 | #include <algorithm> |
28 | |
29 | namespace love |
30 | { |
31 | namespace image |
32 | { |
33 | namespace magpie |
34 | { |
35 | |
36 | namespace |
37 | { |
38 | |
39 | // 'P' 'V' 'R' 3 |
40 | static const uint32 PVRTEX3_IDENT = 0x03525650; |
41 | static const uint32 PVRTEX3_IDENT_REV = 0x50565203; |
42 | |
43 | #pragma pack(push, 4) |
44 | struct |
45 | { |
46 | uint32 ; /// Version of the file header, used to identify it. |
47 | uint32 ; /// Various format flags. |
48 | uint64 ; /// The pixel format, 8cc value storing the 4 channel identifiers and their respective sizes. |
49 | uint32 ; /// The Color Space of the texture, currently either linear RGB or sRGB. |
50 | uint32 ; /// Variable type that the channel is stored in. Supports signed/unsigned int/short/byte or float for now. |
51 | uint32 ; /// Height of the texture. |
52 | uint32 ; /// Width of the texture. |
53 | uint32 ; /// Depth of the texture. (Z-slices) |
54 | uint32 ; /// Number of members in a Texture Array. |
55 | uint32 ; /// Number of faces in a Cube Map. Maybe be a value other than 6. |
56 | uint32 ; /// Number of MIP Maps in the texture - NB: Includes top level. |
57 | uint32 ; /// Size of the accompanying meta data. |
58 | }; |
59 | #pragma pack(pop) |
60 | |
61 | enum PVRV3PixelFormat |
62 | { |
63 | ePVRTPF_PVRTCI_2bpp_RGB = 0, |
64 | ePVRTPF_PVRTCI_2bpp_RGBA, |
65 | ePVRTPF_PVRTCI_4bpp_RGB, |
66 | ePVRTPF_PVRTCI_4bpp_RGBA, |
67 | ePVRTPF_PVRTCII_2bpp = 4, |
68 | ePVRTPF_PVRTCII_4bpp, |
69 | ePVRTPF_ETC1 = 6, |
70 | ePVRTPF_DXT1 = 7, |
71 | ePVRTPF_DXT2, |
72 | ePVRTPF_DXT3, |
73 | ePVRTPF_DXT4, |
74 | ePVRTPF_DXT5, |
75 | ePVRTPF_BC4, |
76 | ePVRTPF_BC5, |
77 | ePVRTPF_BC6, |
78 | ePVRTPF_BC7, |
79 | ePVRTPF_ETC2_RGB = 22, |
80 | ePVRTPF_ETC2_RGBA, |
81 | ePVRTPF_ETC2_RGBA1, |
82 | ePVRTPF_EAC_R = 25, |
83 | ePVRTPF_EAC_RG, |
84 | ePVRTPF_ASTC_4x4 = 27, |
85 | ePVRTPF_ASTC_5x4, |
86 | ePVRTPF_ASTC_5x5, |
87 | ePVRTPF_ASTC_6x5, |
88 | ePVRTPF_ASTC_6x6, |
89 | ePVRTPF_ASTC_8x5, |
90 | ePVRTPF_ASTC_8x6, |
91 | ePVRTPF_ASTC_8x8, |
92 | ePVRTPF_ASTC_10x5, |
93 | ePVRTPF_ASTC_10x6, |
94 | ePVRTPF_ASTC_10x8, |
95 | ePVRTPF_ASTC_10x10, |
96 | ePVRTPF_ASTC_12x10, |
97 | ePVRTPF_ASTC_12x12, |
98 | ePVRTPF_UNKNOWN_FORMAT = 0x7F |
99 | }; |
100 | |
101 | enum PVRV3ChannelType |
102 | { |
103 | ePVRTCT_UNORM8 = 0, |
104 | ePVRTCT_SNORM8, |
105 | ePVRTCT_UINT8, |
106 | ePVRTCT_SINT8, |
107 | ePVRTCT_UNORM16, |
108 | ePVRTCT_SNORM16, |
109 | ePVRTCT_UINT16, |
110 | ePVRTCT_SINT16, |
111 | ePVRTCT_UNORM32, |
112 | ePVRTCT_SNORM32, |
113 | ePVRTCT_UINT32, |
114 | ePVRTCT_SINT32, |
115 | ePVRTCT_FLOAT |
116 | }; |
117 | |
118 | // 'P' 'V' 'R' '!' |
119 | static const uint32 PVRTEX2_IDENT = 0x21525650; |
120 | static const uint32 PVRTEX2_IDENT_REV = 0x50565221; |
121 | |
122 | struct |
123 | { |
124 | uint32 ; |
125 | uint32 ; |
126 | uint32 ; |
127 | uint32 ; |
128 | uint32 ; |
129 | uint32 ; |
130 | uint32 ; |
131 | uint32 ; |
132 | uint32 ; |
133 | uint32 ; |
134 | uint32 ; |
135 | uint32 ; |
136 | uint32 ; |
137 | }; |
138 | |
139 | // The legacy V2 pixel types we support. |
140 | enum PVRPixelTypeV2 |
141 | { |
142 | PixelTypePVRTC2 = 0x18, |
143 | PixelTypePVRTC4, |
144 | PixelTypePVRTCII2 = 0x1C, |
145 | PixelTypePVRTCII4, |
146 | PixelTypeDXT1 = 0x20, |
147 | PixelTypeDXT3 = 0x22, |
148 | PixelTypeDXT5 = 0x24, |
149 | PixelTypeETC1 = 0x36 |
150 | }; |
151 | |
152 | // Convert a V2 header to V3. |
153 | void (PVRTexHeaderV2 , PVRTexHeaderV3 *) |
154 | { |
155 | // If the header's endianness doesn't match our own, we swap everything. |
156 | if (header2.pvrTag == PVRTEX2_IDENT_REV) |
157 | { |
158 | // All of the struct's members are uint32 values, so we can do this. |
159 | uint32 * = (uint32 *) &header2; |
160 | for (size_t i = 0; i < sizeof(PVRTexHeaderV2) / sizeof(uint32); i++) |
161 | headerArray[i] = swapuint32(headerArray[i]); |
162 | } |
163 | |
164 | memset(header3, 0, sizeof(PVRTexHeaderV3)); |
165 | |
166 | header3->version = PVRTEX3_IDENT; |
167 | header3->height = header2.height; |
168 | header3->width = header2.width; |
169 | header3->depth = 1; |
170 | header3->numSurfaces = header2.numSurfaces; |
171 | header3->numFaces = 1; |
172 | header3->numMipmaps = header2.numMipmaps; |
173 | header3->metaDataSize = 0; |
174 | |
175 | switch ((PVRPixelTypeV2) (header2.flags & 0xFF)) |
176 | { |
177 | case PixelTypePVRTC2: |
178 | header3->pixelFormat = ePVRTPF_PVRTCI_2bpp_RGBA; |
179 | break; |
180 | case PixelTypePVRTC4: |
181 | header3->pixelFormat = ePVRTPF_PVRTCI_4bpp_RGBA; |
182 | break; |
183 | case PixelTypePVRTCII2: |
184 | header3->pixelFormat = ePVRTPF_PVRTCII_2bpp; |
185 | break; |
186 | case PixelTypePVRTCII4: |
187 | header3->pixelFormat = ePVRTPF_PVRTCII_4bpp; |
188 | break; |
189 | case PixelTypeDXT1: |
190 | header3->pixelFormat = ePVRTPF_DXT1; |
191 | break; |
192 | case PixelTypeDXT3: |
193 | header3->pixelFormat = ePVRTPF_DXT3; |
194 | break; |
195 | case PixelTypeDXT5: |
196 | header3->pixelFormat = ePVRTPF_DXT5; |
197 | break; |
198 | case PixelTypeETC1: |
199 | header3->pixelFormat = ePVRTPF_ETC1; |
200 | break; |
201 | default: |
202 | header3->pixelFormat = ePVRTPF_UNKNOWN_FORMAT; |
203 | break; |
204 | } |
205 | } |
206 | |
207 | static PixelFormat convertFormat(PVRV3PixelFormat format, PVRV3ChannelType channeltype) |
208 | { |
209 | bool snorm = false; |
210 | |
211 | switch (channeltype) |
212 | { |
213 | case ePVRTCT_SNORM8: |
214 | case ePVRTCT_SNORM16: |
215 | case ePVRTCT_SNORM32: |
216 | snorm = true; |
217 | break; |
218 | default: |
219 | break; |
220 | } |
221 | |
222 | switch (format) |
223 | { |
224 | case ePVRTPF_PVRTCI_2bpp_RGB: |
225 | return PIXELFORMAT_PVR1_RGB2; |
226 | case ePVRTPF_PVRTCI_2bpp_RGBA: |
227 | return PIXELFORMAT_PVR1_RGBA2; |
228 | case ePVRTPF_PVRTCI_4bpp_RGB: |
229 | return PIXELFORMAT_PVR1_RGB4; |
230 | case ePVRTPF_PVRTCI_4bpp_RGBA: |
231 | return PIXELFORMAT_PVR1_RGBA4; |
232 | case ePVRTPF_ETC1: |
233 | return PIXELFORMAT_ETC1; |
234 | case ePVRTPF_DXT1: |
235 | return PIXELFORMAT_DXT1; |
236 | case ePVRTPF_DXT3: |
237 | return PIXELFORMAT_DXT3; |
238 | case ePVRTPF_DXT5: |
239 | return PIXELFORMAT_DXT5; |
240 | case ePVRTPF_BC4: |
241 | return snorm ? PIXELFORMAT_BC4s : PIXELFORMAT_BC4; |
242 | case ePVRTPF_BC5: |
243 | return snorm ? PIXELFORMAT_BC5s : PIXELFORMAT_BC5; |
244 | case ePVRTPF_BC6: |
245 | return snorm ? PIXELFORMAT_BC6Hs : PIXELFORMAT_BC6H; |
246 | case ePVRTPF_BC7: |
247 | return PIXELFORMAT_BC7; |
248 | case ePVRTPF_ETC2_RGB: |
249 | return PIXELFORMAT_ETC2_RGB; |
250 | case ePVRTPF_ETC2_RGBA: |
251 | return PIXELFORMAT_ETC2_RGBA; |
252 | case ePVRTPF_ETC2_RGBA1: |
253 | return PIXELFORMAT_ETC2_RGBA1; |
254 | case ePVRTPF_EAC_R: |
255 | return snorm ? PIXELFORMAT_EAC_Rs : PIXELFORMAT_EAC_R; |
256 | case ePVRTPF_EAC_RG: |
257 | return snorm ? PIXELFORMAT_EAC_RGs : PIXELFORMAT_EAC_RG; |
258 | case ePVRTPF_ASTC_4x4: |
259 | return PIXELFORMAT_ASTC_4x4; |
260 | case ePVRTPF_ASTC_5x4: |
261 | return PIXELFORMAT_ASTC_5x4; |
262 | case ePVRTPF_ASTC_5x5: |
263 | return PIXELFORMAT_ASTC_5x5; |
264 | case ePVRTPF_ASTC_6x5: |
265 | return PIXELFORMAT_ASTC_6x5; |
266 | case ePVRTPF_ASTC_6x6: |
267 | return PIXELFORMAT_ASTC_6x6; |
268 | case ePVRTPF_ASTC_8x5: |
269 | return PIXELFORMAT_ASTC_8x5; |
270 | case ePVRTPF_ASTC_8x6: |
271 | return PIXELFORMAT_ASTC_8x6; |
272 | case ePVRTPF_ASTC_8x8: |
273 | return PIXELFORMAT_ASTC_8x8; |
274 | case ePVRTPF_ASTC_10x5: |
275 | return PIXELFORMAT_ASTC_10x5; |
276 | case ePVRTPF_ASTC_10x6: |
277 | return PIXELFORMAT_ASTC_10x6; |
278 | case ePVRTPF_ASTC_10x8: |
279 | return PIXELFORMAT_ASTC_10x8; |
280 | case ePVRTPF_ASTC_10x10: |
281 | return PIXELFORMAT_ASTC_10x10; |
282 | case ePVRTPF_ASTC_12x10: |
283 | return PIXELFORMAT_ASTC_12x10; |
284 | case ePVRTPF_ASTC_12x12: |
285 | return PIXELFORMAT_ASTC_12x12; |
286 | default: |
287 | return PIXELFORMAT_UNKNOWN; |
288 | } |
289 | } |
290 | |
291 | int getBitsPerPixel(uint64 pixelformat) |
292 | { |
293 | // Uncompressed formats have their bits per pixel stored in the high bits. |
294 | if ((pixelformat & 0xFFFFFFFF) != pixelformat) |
295 | { |
296 | const uint8 *charformat = (const uint8 *) &pixelformat; |
297 | return charformat[4] + charformat[5] + charformat[6] + charformat[7]; |
298 | } |
299 | |
300 | switch (pixelformat) |
301 | { |
302 | case ePVRTPF_PVRTCI_2bpp_RGB: |
303 | case ePVRTPF_PVRTCI_2bpp_RGBA: |
304 | case ePVRTPF_PVRTCII_2bpp: |
305 | return 2; |
306 | case ePVRTPF_PVRTCI_4bpp_RGB: |
307 | case ePVRTPF_PVRTCI_4bpp_RGBA: |
308 | case ePVRTPF_PVRTCII_4bpp: |
309 | case ePVRTPF_ETC1: |
310 | case ePVRTPF_DXT1: |
311 | case ePVRTPF_BC4: |
312 | case ePVRTPF_ETC2_RGB: |
313 | case ePVRTPF_ETC2_RGBA1: |
314 | case ePVRTPF_EAC_R: |
315 | return 4; |
316 | case ePVRTPF_DXT2: |
317 | case ePVRTPF_DXT3: |
318 | case ePVRTPF_DXT4: |
319 | case ePVRTPF_DXT5: |
320 | case ePVRTPF_BC5: |
321 | case ePVRTPF_BC6: |
322 | case ePVRTPF_BC7: |
323 | case ePVRTPF_ETC2_RGBA: |
324 | case ePVRTPF_EAC_RG: |
325 | return 8; |
326 | default: |
327 | return 0; |
328 | } |
329 | } |
330 | |
331 | void getFormatMinDimensions(uint64 pixelformat, int &minX, int &minY, int &minZ) |
332 | { |
333 | minZ = 1; |
334 | |
335 | switch (pixelformat) |
336 | { |
337 | case ePVRTPF_PVRTCI_2bpp_RGB: |
338 | case ePVRTPF_PVRTCI_2bpp_RGBA: |
339 | minX = 16; |
340 | minY = 8; |
341 | break; |
342 | case ePVRTPF_PVRTCI_4bpp_RGB: |
343 | case ePVRTPF_PVRTCI_4bpp_RGBA: |
344 | minX = minY = 8; |
345 | break; |
346 | case ePVRTPF_PVRTCII_2bpp: |
347 | minX = 8; |
348 | minY = 4; |
349 | break; |
350 | case ePVRTPF_PVRTCII_4bpp: |
351 | minX = minY = 4; |
352 | break; |
353 | case ePVRTPF_DXT1: |
354 | case ePVRTPF_DXT2: |
355 | case ePVRTPF_DXT3: |
356 | case ePVRTPF_DXT4: |
357 | case ePVRTPF_DXT5: |
358 | case ePVRTPF_BC4: |
359 | case ePVRTPF_BC5: |
360 | case ePVRTPF_BC6: |
361 | case ePVRTPF_BC7: |
362 | case ePVRTPF_ETC1: |
363 | case ePVRTPF_ETC2_RGB: |
364 | case ePVRTPF_ETC2_RGBA: |
365 | case ePVRTPF_ETC2_RGBA1: |
366 | case ePVRTPF_EAC_R: |
367 | case ePVRTPF_EAC_RG: |
368 | minX = minY = 4; |
369 | break; |
370 | case ePVRTPF_ASTC_4x4: |
371 | minX = 4; |
372 | minY = 4; |
373 | break; |
374 | case ePVRTPF_ASTC_5x4: |
375 | minX = 5; |
376 | minY = 4; |
377 | break; |
378 | case ePVRTPF_ASTC_5x5: |
379 | minX = 5; |
380 | minY = 5; |
381 | break; |
382 | case ePVRTPF_ASTC_6x5: |
383 | minX = 6; |
384 | minY = 5; |
385 | break; |
386 | case ePVRTPF_ASTC_6x6: |
387 | minX = 6; |
388 | minY = 6; |
389 | break; |
390 | case ePVRTPF_ASTC_8x5: |
391 | minX = 8; |
392 | minY = 5; |
393 | break; |
394 | case ePVRTPF_ASTC_8x6: |
395 | minX = 8; |
396 | minY = 6; |
397 | break; |
398 | case ePVRTPF_ASTC_8x8: |
399 | minX = 8; |
400 | minY = 8; |
401 | break; |
402 | case ePVRTPF_ASTC_10x5: |
403 | minX = 10; |
404 | minY = 5; |
405 | break; |
406 | case ePVRTPF_ASTC_10x6: |
407 | minX = 10; |
408 | minY = 6; |
409 | break; |
410 | case ePVRTPF_ASTC_10x8: |
411 | minX = 10; |
412 | minY = 8; |
413 | break; |
414 | case ePVRTPF_ASTC_10x10: |
415 | minX = 10; |
416 | minY = 10; |
417 | break; |
418 | case ePVRTPF_ASTC_12x10: |
419 | minX = 12; |
420 | minY = 10; |
421 | break; |
422 | case ePVRTPF_ASTC_12x12: |
423 | minX = 12; |
424 | minY = 12; |
425 | break; |
426 | default: // We don't handle all possible formats, but that's fine. |
427 | minX = minY = 1; |
428 | break; |
429 | } |
430 | } |
431 | |
432 | size_t (const PVRTexHeaderV3 &, int miplevel) |
433 | { |
434 | int smallestwidth = 1; |
435 | int smallestheight = 1; |
436 | int smallestdepth = 1; |
437 | getFormatMinDimensions(header.pixelFormat, smallestwidth, smallestheight, smallestdepth); |
438 | |
439 | int width = std::max((int) header.width >> miplevel, 1); |
440 | int height = std::max((int) header.height >> miplevel, 1); |
441 | int depth = std::max((int) header.depth >> miplevel, 1); |
442 | |
443 | // Pad the dimensions. |
444 | width = ((width + smallestwidth - 1) / smallestwidth) * smallestwidth; |
445 | height = ((height + smallestheight - 1) / smallestheight) * smallestheight; |
446 | depth = ((depth + smallestdepth - 1) / smallestdepth) * smallestdepth; |
447 | |
448 | if (header.pixelFormat >= ePVRTPF_ASTC_4x4 && header.pixelFormat <= ePVRTPF_ASTC_12x12) |
449 | return (width / smallestwidth) * (height / smallestheight) * (depth / smallestdepth) * (128 / 8); |
450 | else |
451 | return getBitsPerPixel(header.pixelFormat) * width * height * depth / 8; |
452 | } |
453 | |
454 | } // Anonymous namespace. |
455 | |
456 | |
457 | bool PVRHandler::canParseCompressed(Data *data) |
458 | { |
459 | if (data->getSize() < sizeof(PVRTexHeaderV2) || data->getSize() < sizeof(PVRTexHeaderV3)) |
460 | return false; |
461 | |
462 | PVRTexHeaderV3 * = (PVRTexHeaderV3 *) data->getData(); |
463 | |
464 | // Magic number (FourCC identifier.) |
465 | if (header3->version == PVRTEX3_IDENT || header3->version == PVRTEX3_IDENT_REV) |
466 | return true; |
467 | |
468 | // Maybe it has a V2 header. |
469 | PVRTexHeaderV2 * = (PVRTexHeaderV2 *) data->getData(); |
470 | |
471 | // FourCC identifier. |
472 | if (header2->pvrTag == PVRTEX2_IDENT || header2->pvrTag == PVRTEX2_IDENT_REV) |
473 | return true; |
474 | |
475 | return false; |
476 | } |
477 | |
478 | StrongRef<CompressedMemory> PVRHandler::parseCompressed(Data *filedata, std::vector<StrongRef<CompressedSlice>> &images, PixelFormat &format, bool &sRGB) |
479 | { |
480 | if (!canParseCompressed(filedata)) |
481 | throw love::Exception("Could not decode compressed data (not a PVR file?)" ); |
482 | |
483 | PVRTexHeaderV3 = *(PVRTexHeaderV3 *) filedata->getData(); |
484 | |
485 | // If the header isn't the V3 format, assume it's V2 and convert. |
486 | if (header3.version != PVRTEX3_IDENT && header3.version != PVRTEX3_IDENT_REV) |
487 | ConvertPVRHeader(*(PVRTexHeaderV2 *) filedata->getData(), &header3); |
488 | |
489 | // If the header's endianness doesn't match our own, then we swap everything. |
490 | if (header3.version == PVRTEX3_IDENT_REV) |
491 | { |
492 | header3.version = PVRTEX3_IDENT; |
493 | header3.flags = swapuint32(header3.flags); |
494 | header3.pixelFormat = swapuint64(header3.pixelFormat); |
495 | header3.colorSpace = swapuint32(header3.colorSpace); |
496 | header3.channelType = swapuint32(header3.channelType); |
497 | header3.height = swapuint32(header3.height); |
498 | header3.width = swapuint32(header3.width); |
499 | header3.depth = swapuint32(header3.depth); |
500 | header3.numFaces = swapuint32(header3.numFaces); |
501 | header3.numMipmaps = swapuint32(header3.numMipmaps); |
502 | header3.metaDataSize = swapuint32(header3.metaDataSize); |
503 | } |
504 | |
505 | if (header3.depth > 1) |
506 | throw love::Exception("Image depths greater than 1 in PVR files are unsupported." ); |
507 | |
508 | PVRV3PixelFormat pixelformat = (PVRV3PixelFormat) header3.pixelFormat; |
509 | PVRV3ChannelType channeltype = (PVRV3ChannelType) header3.channelType; |
510 | |
511 | PixelFormat cformat = convertFormat(pixelformat, channeltype); |
512 | |
513 | if (cformat == PIXELFORMAT_UNKNOWN) |
514 | throw love::Exception("Could not parse PVR file: unsupported image format." ); |
515 | |
516 | size_t totalsize = 0; |
517 | |
518 | // Ignore faces and surfaces except the first ones (for now.) |
519 | for (int i = 0; i < (int) header3.numMipmaps; i++) |
520 | totalsize += getMipLevelSize(header3, i); |
521 | |
522 | size_t fileoffset = sizeof(PVRTexHeaderV3) + header3.metaDataSize; |
523 | |
524 | // Make sure the file actually holds this much data... |
525 | if (filedata->getSize() < fileoffset + totalsize) |
526 | throw love::Exception("Could not parse PVR file: invalid size calculation." ); |
527 | |
528 | StrongRef<CompressedMemory> memory; |
529 | memory.set(new CompressedMemory(totalsize), Acquire::NORETAIN); |
530 | |
531 | size_t curoffset = 0; |
532 | const uint8 *filebytes = (uint8 *) filedata->getData() + fileoffset; |
533 | |
534 | for (int i = 0; i < (int) header3.numMipmaps; i++) |
535 | { |
536 | size_t mipsize = getMipLevelSize(header3, i); |
537 | |
538 | if (curoffset + mipsize > totalsize) |
539 | break; // Just in case. |
540 | |
541 | int width = std::max((int) header3.width >> i, 1); |
542 | int height = std::max((int) header3.height >> i, 1); |
543 | |
544 | memcpy(memory->data + curoffset, filebytes + curoffset, mipsize); |
545 | |
546 | auto slice = new CompressedSlice(cformat, width, height, memory, curoffset, mipsize); |
547 | images.push_back(slice); |
548 | slice->release(); |
549 | |
550 | curoffset += mipsize; |
551 | } |
552 | |
553 | format = cformat; |
554 | sRGB = (header3.colorSpace == 1); |
555 | |
556 | return memory; |
557 | } |
558 | |
559 | } // magpie |
560 | } // image |
561 | } // love |
562 | |