/* * DDS GIMP plugin * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; see the file COPYING. If not, write to * the Free Software Foundation, 51 Franklin Street, Fifth Floor * Boston, MA 02110-1301, USA. */ /* ImageMagick's implementation of BC7 was referenced for our implementation. * The relevant commit: https://github.com/ImageMagick/ImageMagick/pull/4126/files */ #include #include #include #include #include #include "bc7.h" #define SWAP(a, b) do { typeof(a) t; t = a; a = b; b = t; } while(0) static guchar get_bits (const guchar *block, guchar *start_bit, guchar length); static void decode_rgba_channels (BC7_colors *colors, guchar *src, guint mode, guchar *start); static gboolean is_pixel_anchor (guchar subset_index, guint precision, guchar pixel_index, guint partition_id); gint bc7_decompress (guchar *src, guint size, guchar *block) { /* BC7 blocks are always 16 bytes */ guchar s[16]; BC7_colors colors; guchar rgb_indexes[16]; guchar alpha_indexes[16]; guchar subset_indexes[16]; guchar rgba[4]; guint mode = 0; guint subset_count = 0; guint partition_id = 0; guint swap = 0; guint selector = 0; guchar current_bit = 0; guint i_precision = 0; guint no_sel_precision = 0; for (gint i = 0; i < 16; i++) s[i] = src[i]; /* BC7 blocks are read from the last bit of the last byte, backwards. * The mode is determined by the first 1 bit encountered. For instance, * 1 is mode 0, 10 is mode 1, 100 is mode 2, and so on. */ while (current_bit <= 8 && ! get_bits (s, ¤t_bit, 1)) { continue; } mode = current_bit - 1; if (mode > 7) return 0; /* For modes that support partitions, we start counting * from left of the mode bit, and get the partition ID. * The size of the partition ID is defined by the mode. */ subset_count = mode_info[mode].subset_count; if (subset_count > 1) { partition_id = get_bits (s, ¤t_bit, mode_info[mode].partition_bits); if (partition_id > 63) return 0; } /* Mode 4 and 5 might swap channels for better compression. 2 bits after the mode bit * indicate which were swapped (if any) */ if (mode == 4 || mode == 5) swap = get_bits (s, ¤t_bit, 2); /* Mode 4 also has a single selector bit, to the left of its swap bits. * This bit determines where the alpha channel indexes are located. */ if (mode == 4) selector = get_bits (s, ¤t_bit, 1); /* Channel values are stored in RnGnBn(An) format, where the mode determines * how many bits of precision and how many values are stored. */ decode_rgba_channels (&colors, s, mode, ¤t_bit); /* Next, we get the indexes to assemble pixels from the channel values. */ i_precision = mode_info[mode].index_precision; no_sel_precision = mode_info[mode].no_sel_precision; if (mode == 4 && selector) { i_precision = 3; alpha_indexes[0] = get_bits (s, ¤t_bit, 1); for (gint i = 1; i < 16; i++) alpha_indexes[i] = get_bits (s, ¤t_bit, 2); } /* First, calculate the RGB channel indexes based on the partition ID * and the number of subsets. */ for (gint i = 0; i < 16; i++) { guint precision = i_precision; if (subset_count == 2) subset_indexes[i] = partition_table[0][partition_id][i]; else if (subset_count == 3) subset_indexes[i] = partition_table[1][partition_id][i]; else subset_indexes[i] = 0; if (is_pixel_anchor (subset_indexes[i], subset_count, i, partition_id)) precision--; rgb_indexes[i]= get_bits (s, ¤t_bit, precision); } if (mode == 5 || (mode == 4 && ! selector)) { alpha_indexes[0] = get_bits (s, ¤t_bit, no_sel_precision - 1); for (gint i = 1; i < 16; i++) alpha_indexes[i] = get_bits (s, ¤t_bit, no_sel_precision); } /* Create pixels from subset indexes */ for (gint i = 0; i < 16; i++) { guint weight; guint c0 = 2 * subset_indexes[i]; guint c1 = (2 * subset_indexes[i]) + 1; if (i_precision == 2) weight = weight_2[rgb_indexes[i]]; else if (i_precision == 3) weight = weight_3[rgb_indexes[i]]; else weight = weight_4[rgb_indexes[i]]; rgba[0] = ((64 - weight) * colors.r[c0] + weight * colors.r[c1] + 32) >> 6; rgba[1] = ((64 - weight) * colors.g[c0] + weight * colors.g[c1] + 32) >> 6; rgba[2] = ((64 - weight) * colors.b[c0] + weight * colors.b[c1] + 32) >> 6; if (mode == 4 || mode == 5) { weight = weight_2[alpha_indexes[i]]; if (mode == 4 && ! selector) weight = weight_3[alpha_indexes[i]]; } rgba[3] = ((64 - weight) * colors.a[c0] + weight * colors.a[c1] + 32) >> 6; switch (swap) { case 1: SWAP (rgba[3], rgba[0]); break; case 2: SWAP (rgba[3], rgba[1]); break; case 3: SWAP (rgba[3], rgba[2]); break; default: break; } for (gint j = 0; j < 4; j++) block[(i * 4) + j] = rgba[j]; } return 1; } /* Private Functions */ static guchar get_bits (const guchar *block, guchar *start_bit, guchar length) { guchar bits; guint index; guint base; guint first_bits; guint next_bits; index = (*start_bit) >> 3; base = (*start_bit) - (index << 3); if (index > 15) return 0; if (base + length > 8) { first_bits = 8 - base; next_bits = length - first_bits; bits = block[index] >> base; bits |= (block[index + 1] & ((1 << next_bits) - 1)) << first_bits; } else { bits = (block[index] >> base) & ((1 << length) - 1); } (*start_bit) += length; return bits; } static void decode_rgba_channels (BC7_colors *colors, guchar *src, guint mode, guchar *start) { guint channel_count = mode_info[mode].subset_count * 2; guint rgb_precision = mode_info[mode].rgb_precision; guint alpha_precision = mode_info[mode].alpha_precision; /* Get RGB channel values */ for (gint i = 0; i < channel_count; i++) colors->r[i] = get_bits (src, start, rgb_precision); for (gint i = 0; i < channel_count; i++) colors->g[i] = get_bits (src, start, rgb_precision); for (gint i = 0; i < channel_count; i++) colors->b[i] = get_bits (src, start, rgb_precision); /* Modes 4 - 7 also have alpha values */ if (alpha_precision) { for (gint i = 0; i < channel_count; i++) colors->a[i] = get_bits (src, start, alpha_precision); } else { for (gint i = 0; i < channel_count; i++) colors->a[i] = 255; } /* Modes 0, 1, 3, 6, and 7 have P-bits, which increase the precision * by 1. */ if (mode_info[mode].p_bit_count) { rgb_precision++; if (alpha_precision) alpha_precision++; /* P-bits are added at the least significant bit, so * we have to shift existing channel values over by 1 */ for (gint i = 0; i < channel_count; i++) { colors->r[i] <<= 1; colors->g[i] <<= 1; colors->b[i] <<= 1; if (alpha_precision) colors->a[i] <<= 1; } if (mode == 1) { guint p_bit_1 = get_bits (src, start, 1); guint p_bit_2 = get_bits (src, start, 1); for (gint i = 0; i < 2; i++) { colors->r[i] |= p_bit_1; colors->g[i] |= p_bit_1; colors->b[i] |= p_bit_1; colors->r[i + 2] |= p_bit_2; colors->g[i + 2] |= p_bit_2; colors->b[i + 2] |= p_bit_2; } } else { for (gint i = 0; i < channel_count; i++) { guint p_bit = get_bits (src, start, 1); colors->r[i] |= p_bit; colors->g[i] |= p_bit; colors->b[i] |= p_bit; if (alpha_precision) colors->a[i] |= p_bit; } } } /* Normalize all channel values to 8 bits */ for (gint i = 0; i < channel_count; i++) { colors->r[i] <<= (8 - rgb_precision); colors->g[i] <<= (8 - rgb_precision); colors->b[i] <<= (8 - rgb_precision); colors->r[i] = colors->r[i] | (colors->r[i] >> rgb_precision); colors->g[i] = colors->g[i] | (colors->g[i] >> rgb_precision); colors->b[i] = colors->b[i] | (colors->b[i] >> rgb_precision); if (alpha_precision) { colors->a[i] <<= (8 - alpha_precision); colors->a[i] = colors->a[i] | (colors->a[i] >> alpha_precision); } } } static gboolean is_pixel_anchor (guchar subset_index, guint precision, guchar pixel_index, guint partition_id) { guint table_index; if (subset_index == 0) table_index = 0; else if (subset_index == 1 && precision == 2) table_index = 1; else if (subset_index == 1 && precision == 3) table_index = 2; else table_index = 3; if (anchor_index_table[table_index][partition_id] == pixel_index) return TRUE; return FALSE; }