1// Copyright 2009 Google Inc.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15#include <ETC1/etc1.h>
16
17#include <string.h>
18
19/* From http://www.khronos.org/registry/gles/extensions/OES/OES_compressed_ETC1_RGB8_texture.txt
20
21 The number of bits that represent a 4x4 texel block is 64 bits if
22 <internalformat> is given by ETC1_RGB8_OES.
23
24 The data for a block is a number of bytes,
25
26 {q0, q1, q2, q3, q4, q5, q6, q7}
27
28 where byte q0 is located at the lowest memory address and q7 at
29 the highest. The 64 bits specifying the block is then represented
30 by the following 64 bit integer:
31
32 int64bit = 256*(256*(256*(256*(256*(256*(256*q0+q1)+q2)+q3)+q4)+q5)+q6)+q7;
33
34 ETC1_RGB8_OES:
35
36 a) bit layout in bits 63 through 32 if diffbit = 0
37
38 63 62 61 60 59 58 57 56 55 54 53 52 51 50 49 48
39 -----------------------------------------------
40 | base col1 | base col2 | base col1 | base col2 |
41 | R1 (4bits)| R2 (4bits)| G1 (4bits)| G2 (4bits)|
42 -----------------------------------------------
43
44 47 46 45 44 43 42 41 40 39 38 37 36 35 34  33  32
45 ---------------------------------------------------
46 | base col1 | base col2 | table  | table  |diff|flip|
47 | B1 (4bits)| B2 (4bits)| cw 1   | cw 2   |bit |bit |
48 ---------------------------------------------------
49
50
51 b) bit layout in bits 63 through 32 if diffbit = 1
52
53 63 62 61 60 59 58 57 56 55 54 53 52 51 50 49 48
54 -----------------------------------------------
55 | base col1    | dcol 2 | base col1    | dcol 2 |
56 | R1' (5 bits) | dR2    | G1' (5 bits) | dG2    |
57 -----------------------------------------------
58
59 47 46 45 44 43 42 41 40 39 38 37 36 35 34  33  32
60 ---------------------------------------------------
61 | base col 1   | dcol 2 | table  | table  |diff|flip|
62 | B1' (5 bits) | dB2    | cw 1   | cw 2   |bit |bit |
63 ---------------------------------------------------
64
65
66 c) bit layout in bits 31 through 0 (in both cases)
67
68 31 30 29 28 27 26 25 24 23 22 21 20 19 18 17 16
69 -----------------------------------------------
70 |       most significant pixel index bits       |
71 | p| o| n| m| l| k| j| i| h| g| f| e| d| c| b| a|
72 -----------------------------------------------
73
74 15 14 13 12 11 10  9  8  7  6  5  4  3   2   1  0
75 --------------------------------------------------
76 |         least significant pixel index bits       |
77 | p| o| n| m| l| k| j| i| h| g| f| e| d| c | b | a |
78 --------------------------------------------------
79
80
81 Add table 3.17.2: Intensity modifier sets for ETC1 compressed textures:
82
83 table codeword                modifier table
84 ------------------        ----------------------
85 0                     -8  -2  2   8
86 1                    -17  -5  5  17
87 2                    -29  -9  9  29
88 3                    -42 -13 13  42
89 4                    -60 -18 18  60
90 5                    -80 -24 24  80
91 6                   -106 -33 33 106
92 7                   -183 -47 47 183
93
94
95 Add table 3.17.3 Mapping from pixel index values to modifier values for
96 ETC1 compressed textures:
97
98 pixel index value
99 ---------------
100 msb     lsb           resulting modifier value
101 -----   -----          -------------------------
102 1       1            -b (large negative value)
103 1       0            -a (small negative value)
104 0       0             a (small positive value)
105 0       1             b (large positive value)
106
107
108 */
109
110static const int kModifierTable[] = {
111/* 0 */2, 8, -2, -8,
112/* 1 */5, 17, -5, -17,
113/* 2 */9, 29, -9, -29,
114/* 3 */13, 42, -13, -42,
115/* 4 */18, 60, -18, -60,
116/* 5 */24, 80, -24, -80,
117/* 6 */33, 106, -33, -106,
118/* 7 */47, 183, -47, -183 };
119
120static const int kLookup[8] = { 0, 1, 2, 3, -4, -3, -2, -1 };
121
122static inline etc1_byte clamp(int x) {
123    return (etc1_byte) (x >= 0 ? (x < 255 ? x : 255) : 0);
124}
125
126static
127inline int convert4To8(int b) {
128    int c = b & 0xf;
129    return (c << 4) | c;
130}
131
132static
133inline int convert5To8(int b) {
134    int c = b & 0x1f;
135    return (c << 3) | (c >> 2);
136}
137
138static
139inline int convert6To8(int b) {
140    int c = b & 0x3f;
141    return (c << 2) | (c >> 4);
142}
143
144static
145inline int divideBy255(int d) {
146    return (d + 128 + (d >> 8)) >> 8;
147}
148
149static
150inline int convert8To4(int b) {
151    int c = b & 0xff;
152    return divideBy255(c * 15);
153}
154
155static
156inline int convert8To5(int b) {
157    int c = b & 0xff;
158    return divideBy255(c * 31);
159}
160
161static
162inline int convertDiff(int base, int diff) {
163    return convert5To8((0x1f & base) + kLookup[0x7 & diff]);
164}
165
166static
167void decode_subblock(etc1_byte* pOut, int r, int g, int b, const int* table,
168        etc1_uint32 low, bool second, bool flipped) {
169    int baseX = 0;
170    int baseY = 0;
171    if (second) {
172        if (flipped) {
173            baseY = 2;
174        } else {
175            baseX = 2;
176        }
177    }
178    for (int i = 0; i < 8; i++) {
179        int x, y;
180        if (flipped) {
181            x = baseX + (i >> 1);
182            y = baseY + (i & 1);
183        } else {
184            x = baseX + (i >> 2);
185            y = baseY + (i & 3);
186        }
187        int k = y + (x * 4);
188        int offset = ((low >> k) & 1) | ((low >> (k + 15)) & 2);
189        int delta = table[offset];
190        etc1_byte* q = pOut + 3 * (x + 4 * y);
191        *q++ = clamp(r + delta);
192        *q++ = clamp(g + delta);
193        *q++ = clamp(b + delta);
194    }
195}
196
197// Input is an ETC1 compressed version of the data.
198// Output is a 4 x 4 square of 3-byte pixels in form R, G, B
199
200void etc1_decode_block(const etc1_byte* pIn, etc1_byte* pOut) {
201    etc1_uint32 high = (pIn[0] << 24) | (pIn[1] << 16) | (pIn[2] << 8) | pIn[3];
202    etc1_uint32 low = (pIn[4] << 24) | (pIn[5] << 16) | (pIn[6] << 8) | pIn[7];
203    int r1, r2, g1, g2, b1, b2;
204    if (high & 2) {
205        // differential
206        int rBase = high >> 27;
207        int gBase = high >> 19;
208        int bBase = high >> 11;
209        r1 = convert5To8(rBase);
210        r2 = convertDiff(rBase, high >> 24);
211        g1 = convert5To8(gBase);
212        g2 = convertDiff(gBase, high >> 16);
213        b1 = convert5To8(bBase);
214        b2 = convertDiff(bBase, high >> 8);
215    } else {
216        // not differential
217        r1 = convert4To8(high >> 28);
218        r2 = convert4To8(high >> 24);
219        g1 = convert4To8(high >> 20);
220        g2 = convert4To8(high >> 16);
221        b1 = convert4To8(high >> 12);
222        b2 = convert4To8(high >> 8);
223    }
224    int tableIndexA = 7 & (high >> 5);
225    int tableIndexB = 7 & (high >> 2);
226    const int* tableA = kModifierTable + tableIndexA * 4;
227    const int* tableB = kModifierTable + tableIndexB * 4;
228    bool flipped = (high & 1) != 0;
229    decode_subblock(pOut, r1, g1, b1, tableA, low, false, flipped);
230    decode_subblock(pOut, r2, g2, b2, tableB, low, true, flipped);
231}
232
233typedef struct {
234    etc1_uint32 high;
235    etc1_uint32 low;
236    etc1_uint32 score; // Lower is more accurate
237} etc_compressed;
238
239static
240inline void take_best(etc_compressed* a, const etc_compressed* b) {
241    if (a->score > b->score) {
242        *a = *b;
243    }
244}
245
246static
247void etc_average_colors_subblock(const etc1_byte* pIn, etc1_uint32 inMask,
248        etc1_byte* pColors, bool flipped, bool second) {
249    int r = 0;
250    int g = 0;
251    int b = 0;
252
253    if (flipped) {
254        int by = 0;
255        if (second) {
256            by = 2;
257        }
258        for (int y = 0; y < 2; y++) {
259            int yy = by + y;
260            for (int x = 0; x < 4; x++) {
261                int i = x + 4 * yy;
262                if (inMask & (1 << i)) {
263                    const etc1_byte* p = pIn + i * 3;
264                    r += *(p++);
265                    g += *(p++);
266                    b += *(p++);
267                }
268            }
269        }
270    } else {
271        int bx = 0;
272        if (second) {
273            bx = 2;
274        }
275        for (int y = 0; y < 4; y++) {
276            for (int x = 0; x < 2; x++) {
277                int xx = bx + x;
278                int i = xx + 4 * y;
279                if (inMask & (1 << i)) {
280                    const etc1_byte* p = pIn + i * 3;
281                    r += *(p++);
282                    g += *(p++);
283                    b += *(p++);
284                }
285            }
286        }
287    }
288    pColors[0] = (etc1_byte)((r + 4) >> 3);
289    pColors[1] = (etc1_byte)((g + 4) >> 3);
290    pColors[2] = (etc1_byte)((b + 4) >> 3);
291}
292
293static
294inline int square(int x) {
295    return x * x;
296}
297
298static etc1_uint32 chooseModifier(const etc1_byte* pBaseColors,
299        const etc1_byte* pIn, etc1_uint32 *pLow, int bitIndex,
300        const int* pModifierTable) {
301    etc1_uint32 bestScore = ~0;
302    int bestIndex = 0;
303    int pixelR = pIn[0];
304    int pixelG = pIn[1];
305    int pixelB = pIn[2];
306    int r = pBaseColors[0];
307    int g = pBaseColors[1];
308    int b = pBaseColors[2];
309    for (int i = 0; i < 4; i++) {
310        int modifier = pModifierTable[i];
311        int decodedG = clamp(g + modifier);
312        etc1_uint32 score = (etc1_uint32) (6 * square(decodedG - pixelG));
313        if (score >= bestScore) {
314            continue;
315        }
316        int decodedR = clamp(r + modifier);
317        score += (etc1_uint32) (3 * square(decodedR - pixelR));
318        if (score >= bestScore) {
319            continue;
320        }
321        int decodedB = clamp(b + modifier);
322        score += (etc1_uint32) square(decodedB - pixelB);
323        if (score < bestScore) {
324            bestScore = score;
325            bestIndex = i;
326        }
327    }
328    etc1_uint32 lowMask = (((bestIndex >> 1) << 16) | (bestIndex & 1))
329            << bitIndex;
330    *pLow |= lowMask;
331    return bestScore;
332}
333
334static
335void etc_encode_subblock_helper(const etc1_byte* pIn, etc1_uint32 inMask,
336        etc_compressed* pCompressed, bool flipped, bool second,
337        const etc1_byte* pBaseColors, const int* pModifierTable) {
338    int score = pCompressed->score;
339    if (flipped) {
340        int by = 0;
341        if (second) {
342            by = 2;
343        }
344        for (int y = 0; y < 2; y++) {
345            int yy = by + y;
346            for (int x = 0; x < 4; x++) {
347                int i = x + 4 * yy;
348                if (inMask & (1 << i)) {
349                    score += chooseModifier(pBaseColors, pIn + i * 3,
350                            &pCompressed->low, yy + x * 4, pModifierTable);
351                }
352            }
353        }
354    } else {
355        int bx = 0;
356        if (second) {
357            bx = 2;
358        }
359        for (int y = 0; y < 4; y++) {
360            for (int x = 0; x < 2; x++) {
361                int xx = bx + x;
362                int i = xx + 4 * y;
363                if (inMask & (1 << i)) {
364                    score += chooseModifier(pBaseColors, pIn + i * 3,
365                            &pCompressed->low, y + xx * 4, pModifierTable);
366                }
367            }
368        }
369    }
370    pCompressed->score = score;
371}
372
373static bool inRange4bitSigned(int color) {
374    return color >= -4 && color <= 3;
375}
376
377static void etc_encodeBaseColors(etc1_byte* pBaseColors,
378        const etc1_byte* pColors, etc_compressed* pCompressed) {
379    int r1, g1, b1, r2, g2, b2; // 8 bit base colors for sub-blocks
380    bool differential;
381    {
382        int r51 = convert8To5(pColors[0]);
383        int g51 = convert8To5(pColors[1]);
384        int b51 = convert8To5(pColors[2]);
385        int r52 = convert8To5(pColors[3]);
386        int g52 = convert8To5(pColors[4]);
387        int b52 = convert8To5(pColors[5]);
388
389        r1 = convert5To8(r51);
390        g1 = convert5To8(g51);
391        b1 = convert5To8(b51);
392
393        int dr = r52 - r51;
394        int dg = g52 - g51;
395        int db = b52 - b51;
396
397        differential = inRange4bitSigned(dr) && inRange4bitSigned(dg)
398                && inRange4bitSigned(db);
399        if (differential) {
400            r2 = convert5To8(r51 + dr);
401            g2 = convert5To8(g51 + dg);
402            b2 = convert5To8(b51 + db);
403            pCompressed->high |= (r51 << 27) | ((7 & dr) << 24) | (g51 << 19)
404                    | ((7 & dg) << 16) | (b51 << 11) | ((7 & db) << 8) | 2;
405        }
406    }
407
408    if (!differential) {
409        int r41 = convert8To4(pColors[0]);
410        int g41 = convert8To4(pColors[1]);
411        int b41 = convert8To4(pColors[2]);
412        int r42 = convert8To4(pColors[3]);
413        int g42 = convert8To4(pColors[4]);
414        int b42 = convert8To4(pColors[5]);
415        r1 = convert4To8(r41);
416        g1 = convert4To8(g41);
417        b1 = convert4To8(b41);
418        r2 = convert4To8(r42);
419        g2 = convert4To8(g42);
420        b2 = convert4To8(b42);
421        pCompressed->high |= (r41 << 28) | (r42 << 24) | (g41 << 20) | (g42
422                << 16) | (b41 << 12) | (b42 << 8);
423    }
424    pBaseColors[0] = r1;
425    pBaseColors[1] = g1;
426    pBaseColors[2] = b1;
427    pBaseColors[3] = r2;
428    pBaseColors[4] = g2;
429    pBaseColors[5] = b2;
430}
431
432static
433void etc_encode_block_helper(const etc1_byte* pIn, etc1_uint32 inMask,
434        const etc1_byte* pColors, etc_compressed* pCompressed, bool flipped) {
435    pCompressed->score = ~0;
436    pCompressed->high = (flipped ? 1 : 0);
437    pCompressed->low = 0;
438
439    etc1_byte pBaseColors[6];
440
441    etc_encodeBaseColors(pBaseColors, pColors, pCompressed);
442
443    int originalHigh = pCompressed->high;
444
445    const int* pModifierTable = kModifierTable;
446    for (int i = 0; i < 8; i++, pModifierTable += 4) {
447        etc_compressed temp;
448        temp.score = 0;
449        temp.high = originalHigh | (i << 5);
450        temp.low = 0;
451        etc_encode_subblock_helper(pIn, inMask, &temp, flipped, false,
452                pBaseColors, pModifierTable);
453        take_best(pCompressed, &temp);
454    }
455    pModifierTable = kModifierTable;
456    etc_compressed firstHalf = *pCompressed;
457    for (int i = 0; i < 8; i++, pModifierTable += 4) {
458        etc_compressed temp;
459        temp.score = firstHalf.score;
460        temp.high = firstHalf.high | (i << 2);
461        temp.low = firstHalf.low;
462        etc_encode_subblock_helper(pIn, inMask, &temp, flipped, true,
463                pBaseColors + 3, pModifierTable);
464        if (i == 0) {
465            *pCompressed = temp;
466        } else {
467            take_best(pCompressed, &temp);
468        }
469    }
470}
471
472static void writeBigEndian(etc1_byte* pOut, etc1_uint32 d) {
473    pOut[0] = (etc1_byte)(d >> 24);
474    pOut[1] = (etc1_byte)(d >> 16);
475    pOut[2] = (etc1_byte)(d >> 8);
476    pOut[3] = (etc1_byte) d;
477}
478
479// Input is a 4 x 4 square of 3-byte pixels in form R, G, B
480// inmask is a 16-bit mask where bit (1 << (x + y * 4)) tells whether the corresponding (x,y)
481// pixel is valid or not. Invalid pixel color values are ignored when compressing.
482// Output is an ETC1 compressed version of the data.
483
484void etc1_encode_block(const etc1_byte* pIn, etc1_uint32 inMask,
485        etc1_byte* pOut) {
486    etc1_byte colors[6];
487    etc1_byte flippedColors[6];
488    etc_average_colors_subblock(pIn, inMask, colors, false, false);
489    etc_average_colors_subblock(pIn, inMask, colors + 3, false, true);
490    etc_average_colors_subblock(pIn, inMask, flippedColors, true, false);
491    etc_average_colors_subblock(pIn, inMask, flippedColors + 3, true, true);
492
493    etc_compressed a, b;
494    etc_encode_block_helper(pIn, inMask, colors, &a, false);
495    etc_encode_block_helper(pIn, inMask, flippedColors, &b, true);
496    take_best(&a, &b);
497    writeBigEndian(pOut, a.high);
498    writeBigEndian(pOut + 4, a.low);
499}
500
501// Return the size of the encoded image data (does not include size of PKM header).
502
503etc1_uint32 etc1_get_encoded_data_size(etc1_uint32 width, etc1_uint32 height) {
504    return (((width + 3) & ~3) * ((height + 3) & ~3)) >> 1;
505}
506
507// Encode an entire image.
508// pIn - pointer to the image data. Formatted such that the Red component of
509//       pixel (x,y) is at pIn + pixelSize * x + stride * y + redOffset;
510// pOut - pointer to encoded data. Must be large enough to store entire encoded image.
511
512int etc1_encode_image(const etc1_byte* pIn, etc1_uint32 width, etc1_uint32 height,
513        etc1_uint32 pixelSize, etc1_uint32 stride, etc1_byte* pOut) {
514    if (pixelSize < 2 || pixelSize > 3) {
515        return -1;
516    }
517    static const unsigned short kYMask[] = { 0x0, 0xf, 0xff, 0xfff, 0xffff };
518    static const unsigned short kXMask[] = { 0x0, 0x1111, 0x3333, 0x7777,
519            0xffff };
520    etc1_byte block[ETC1_DECODED_BLOCK_SIZE];
521    etc1_byte encoded[ETC1_ENCODED_BLOCK_SIZE];
522
523    etc1_uint32 encodedWidth = (width + 3) & ~3;
524    etc1_uint32 encodedHeight = (height + 3) & ~3;
525
526    for (etc1_uint32 y = 0; y < encodedHeight; y += 4) {
527        etc1_uint32 yEnd = height - y;
528        if (yEnd > 4) {
529            yEnd = 4;
530        }
531        int ymask = kYMask[yEnd];
532        for (etc1_uint32 x = 0; x < encodedWidth; x += 4) {
533            etc1_uint32 xEnd = width - x;
534            if (xEnd > 4) {
535                xEnd = 4;
536            }
537            int mask = ymask & kXMask[xEnd];
538            for (etc1_uint32 cy = 0; cy < yEnd; cy++) {
539                etc1_byte* q = block + (cy * 4) * 3;
540                const etc1_byte* p = pIn + pixelSize * x + stride * (y + cy);
541                if (pixelSize == 3) {
542                    memcpy(q, p, xEnd * 3);
543                } else {
544                    for (etc1_uint32 cx = 0; cx < xEnd; cx++) {
545                        int pixel = (p[1] << 8) | p[0];
546                        *q++ = convert5To8(pixel >> 11);
547                        *q++ = convert6To8(pixel >> 5);
548                        *q++ = convert5To8(pixel);
549                        p += pixelSize;
550                    }
551                }
552            }
553            etc1_encode_block(block, mask, encoded);
554            memcpy(pOut, encoded, sizeof(encoded));
555            pOut += sizeof(encoded);
556        }
557    }
558    return 0;
559}
560
561// Decode an entire image.
562// pIn - pointer to encoded data.
563// pOut - pointer to the image data. Will be written such that the Red component of
564//       pixel (x,y) is at pIn + pixelSize * x + stride * y + redOffset. Must be
565//        large enough to store entire image.
566
567
568int etc1_decode_image(const etc1_byte* pIn, etc1_byte* pOut,
569        etc1_uint32 width, etc1_uint32 height,
570        etc1_uint32 pixelSize, etc1_uint32 stride) {
571    if (pixelSize < 2 || pixelSize > 3) {
572        return -1;
573    }
574    etc1_byte block[ETC1_DECODED_BLOCK_SIZE];
575
576    etc1_uint32 encodedWidth = (width + 3) & ~3;
577    etc1_uint32 encodedHeight = (height + 3) & ~3;
578
579    for (etc1_uint32 y = 0; y < encodedHeight; y += 4) {
580        etc1_uint32 yEnd = height - y;
581        if (yEnd > 4) {
582            yEnd = 4;
583        }
584        for (etc1_uint32 x = 0; x < encodedWidth; x += 4) {
585            etc1_uint32 xEnd = width - x;
586            if (xEnd > 4) {
587                xEnd = 4;
588            }
589            etc1_decode_block(pIn, block);
590            pIn += ETC1_ENCODED_BLOCK_SIZE;
591            for (etc1_uint32 cy = 0; cy < yEnd; cy++) {
592                const etc1_byte* q = block + (cy * 4) * 3;
593                etc1_byte* p = pOut + pixelSize * x + stride * (y + cy);
594                if (pixelSize == 3) {
595                    memcpy(p, q, xEnd * 3);
596                } else {
597                    for (etc1_uint32 cx = 0; cx < xEnd; cx++) {
598                        etc1_byte r = *q++;
599                        etc1_byte g = *q++;
600                        etc1_byte b = *q++;
601                        etc1_uint32 pixel = ((r >> 3) << 11) | ((g >> 2) << 5) | (b >> 3);
602                        *p++ = (etc1_byte) pixel;
603                        *p++ = (etc1_byte) (pixel >> 8);
604                    }
605                }
606            }
607        }
608    }
609    return 0;
610}
611
612static const char kMagic[] = { 'P', 'K', 'M', ' ', '1', '0' };
613
614static const etc1_uint32 ETC1_PKM_FORMAT_OFFSET = 6;
615static const etc1_uint32 ETC1_PKM_ENCODED_WIDTH_OFFSET = 8;
616static const etc1_uint32 ETC1_PKM_ENCODED_HEIGHT_OFFSET = 10;
617static const etc1_uint32 ETC1_PKM_WIDTH_OFFSET = 12;
618static const etc1_uint32 ETC1_PKM_HEIGHT_OFFSET = 14;
619
620static const etc1_uint32 ETC1_RGB_NO_MIPMAPS = 0;
621
622static void writeBEUint16(etc1_byte* pOut, etc1_uint32 data) {
623    pOut[0] = (etc1_byte) (data >> 8);
624    pOut[1] = (etc1_byte) data;
625}
626
627static etc1_uint32 readBEUint16(const etc1_byte* pIn) {
628    return (pIn[0] << 8) | pIn[1];
629}
630
631// Format a PKM header
632
633void etc1_pkm_format_header(etc1_byte* pHeader, etc1_uint32 width, etc1_uint32 height) {
634    memcpy(pHeader, kMagic, sizeof(kMagic));
635    etc1_uint32 encodedWidth = (width + 3) & ~3;
636    etc1_uint32 encodedHeight = (height + 3) & ~3;
637    writeBEUint16(pHeader + ETC1_PKM_FORMAT_OFFSET, ETC1_RGB_NO_MIPMAPS);
638    writeBEUint16(pHeader + ETC1_PKM_ENCODED_WIDTH_OFFSET, encodedWidth);
639    writeBEUint16(pHeader + ETC1_PKM_ENCODED_HEIGHT_OFFSET, encodedHeight);
640    writeBEUint16(pHeader + ETC1_PKM_WIDTH_OFFSET, width);
641    writeBEUint16(pHeader + ETC1_PKM_HEIGHT_OFFSET, height);
642}
643
644// Check if a PKM header is correctly formatted.
645
646etc1_bool etc1_pkm_is_valid(const etc1_byte* pHeader) {
647    if (memcmp(pHeader, kMagic, sizeof(kMagic))) {
648        return false;
649    }
650    etc1_uint32 format = readBEUint16(pHeader + ETC1_PKM_FORMAT_OFFSET);
651    etc1_uint32 encodedWidth = readBEUint16(pHeader + ETC1_PKM_ENCODED_WIDTH_OFFSET);
652    etc1_uint32 encodedHeight = readBEUint16(pHeader + ETC1_PKM_ENCODED_HEIGHT_OFFSET);
653    etc1_uint32 width = readBEUint16(pHeader + ETC1_PKM_WIDTH_OFFSET);
654    etc1_uint32 height = readBEUint16(pHeader + ETC1_PKM_HEIGHT_OFFSET);
655    return format == ETC1_RGB_NO_MIPMAPS &&
656            encodedWidth >= width && encodedWidth - width < 4 &&
657            encodedHeight >= height && encodedHeight - height < 4;
658}
659
660// Read the image width from a PKM header
661
662etc1_uint32 etc1_pkm_get_width(const etc1_byte* pHeader) {
663    return readBEUint16(pHeader + ETC1_PKM_WIDTH_OFFSET);
664}
665
666// Read the image height from a PKM header
667
668etc1_uint32 etc1_pkm_get_height(const etc1_byte* pHeader){
669    return readBEUint16(pHeader + ETC1_PKM_HEIGHT_OFFSET);
670}
671