exr-zig

Use EXR images
git clone git://git.electrosoup.com/exr-zig
Log | Files | Refs

lut.zig (5860B)


      1 //! This module provides functions for compressing the range of a dataset
      2 //! using bitmaps and lookup tables.
      3 
      4 const std = @import("std");
      5 const assert = std.debug.assert;
      6 const expect = std.testing.expect;
      7 
      8 const u16_range = std.math.maxInt(u16) + 1;
      9 const bitmap_size = u16_range >> 3;
     10 
     11 const BitmapResult = struct {
     12     bitmap: []u8,
     13     min_non_zero: u16,
     14     max_non_zero: u16,
     15 };
     16 
     17 fn generateBitmap(
     18     data: []const u16,
     19     allocator: std.mem.Allocator,
     20 ) !BitmapResult {
     21     const bitmap = try allocator.alloc(u8, bitmap_size);
     22     @memset(bitmap, 0);
     23     // array index = upper 13 bits
     24     //   bit index = lower  3 bits
     25     for (data) |x| {
     26         bitmap[x >> 3] |= (@as(u8, 1) << @as(u3, @intCast(x & 7)));
     27     }
     28     // don't store zero, its existence is assumed
     29     bitmap[0] &= 0b11111110;
     30     // compute the first and last non zero bytes
     31     var min_non_zero: u16 = bitmap_size - 1;
     32     var max_non_zero: u16 = 0;
     33     for (0..bitmap_size) |i| {
     34         const j: u16 = @intCast(i);
     35         if (bitmap[j] == 0) continue;
     36         min_non_zero = @min(j, min_non_zero);
     37         max_non_zero = @max(j, max_non_zero);
     38     }
     39     assert(min_non_zero <= max_non_zero);
     40     return .{
     41         .bitmap = bitmap,
     42         .min_non_zero = min_non_zero,
     43         .max_non_zero = max_non_zero,
     44     };
     45 }
     46 
     47 test "bitmap basic functionality" {
     48     const allocator = std.testing.allocator;
     49     const data = [_]u16{ 3, 10, 5, 1 };
     50     const result = try generateBitmap(&data, allocator);
     51     defer allocator.free(result.bitmap);
     52     try expect(result.min_non_zero == 0);
     53     try expect(result.max_non_zero == 1);
     54     //  1 -> byte 0, bit 1 (00000010)
     55     //  3 -> byte 0, bit 3 (00001000)
     56     //  5 -> byte 0, bit 5 (00100000)
     57     // 10 -> byte 1, bit 2 (00000100)
     58     try expect(result.bitmap[0] == 0b00101010);
     59     try expect(result.bitmap[1] == 0b00000100);
     60     for (result.bitmap[2..]) |byte| {
     61         try expect(byte == 0);
     62     }
     63 }
     64 
     65 test "bitmap largest value" {
     66     const allocator = std.testing.allocator;
     67     const data = [_]u16{u16_range - 1};
     68     const result = try generateBitmap(&data, allocator);
     69     defer allocator.free(result.bitmap);
     70     try expect(result.min_non_zero == bitmap_size - 1);
     71     try expect(result.max_non_zero == bitmap_size - 1);
     72     try expect(result.bitmap[bitmap_size - 1] == 0b10000000);
     73     for (result.bitmap[0 .. bitmap_size - 1]) |byte| {
     74         try expect(byte == 0);
     75     }
     76 }
     77 
     78 const LUTResult = struct {
     79     lut: []u16,
     80     num_nonzero_values: u16,
     81 };
     82 
     83 fn generateForwardLUT(
     84     bitmap: []const u8,
     85     allocator: std.mem.Allocator,
     86 ) !LUTResult {
     87     // Map values of set bits to sequential numbers.
     88     const lut = try allocator.alloc(u16, u16_range);
     89     @memset(lut, 0);
     90     var k: u16 = 0;
     91     for (0..u16_range) |i| {
     92         const mask = @as(u8, 1) << @as(u3, @intCast(i & 7));
     93         const bit_is_set = (bitmap[i >> 3] & mask) != 0;
     94         if ((i == 0) or bit_is_set) {
     95             lut[i] = k;
     96             k += 1;
     97         }
     98     }
     99     return .{ .lut = lut, .num_nonzero_values = k - 1 };
    100 }
    101 
    102 test "forward LUT" {
    103     const allocator = std.testing.allocator;
    104     const data = [_]u16{ 3, 10, 5, 1 };
    105     const bitmap = (try generateBitmap(&data, allocator)).bitmap;
    106     defer allocator.free(bitmap);
    107     const result = try generateForwardLUT(bitmap, allocator);
    108     defer allocator.free(result.lut);
    109     try expect(result.num_nonzero_values == 4);
    110     try expect(result.lut[0] == 0);
    111     try expect(result.lut[1] == 1);
    112     try expect(result.lut[3] == 2);
    113     try expect(result.lut[5] == 3);
    114     try expect(result.lut[10] == 4);
    115 }
    116 
    117 fn generateReverseLUT(
    118     bitmap: []const u8,
    119     allocator: std.mem.Allocator,
    120 ) !LUTResult {
    121     // Map sequential numbers to values of set bits.
    122     const lut = try allocator.alloc(u16, u16_range);
    123     @memset(lut, 0);
    124     var k: u16 = 0;
    125     for (0..u16_range) |i| {
    126         const mask = @as(u8, 1) << @as(u3, @intCast(i & 7));
    127         const bit_is_set = (bitmap[i >> 3] & mask) != 0;
    128         if ((i == 0) or bit_is_set) {
    129             lut[k] = @intCast(i);
    130             k += 1;
    131         }
    132     }
    133     return .{ .lut = lut, .num_nonzero_values = k - 1 };
    134 }
    135 
    136 test "reverse LUT" {
    137     const allocator = std.testing.allocator;
    138     const data = [_]u16{ 3, 10, 5, 1 };
    139     const bitmap = (try generateBitmap(&data, allocator)).bitmap;
    140     defer allocator.free(bitmap);
    141     const result = try generateReverseLUT(bitmap, allocator);
    142     defer allocator.free(result.lut);
    143     try expect(result.num_nonzero_values == 4);
    144     try expect(result.lut[0] == 0);
    145     try expect(result.lut[1] == 1);
    146     try expect(result.lut[2] == 3);
    147     try expect(result.lut[3] == 5);
    148     try expect(result.lut[4] == 10);
    149 }
    150 
    151 fn applyLUT(lut: []const u16, data: []u16) void {
    152     for (data) |*x| {
    153         x.* = lut[x.*];
    154     }
    155 }
    156 
    157 test "apply forward LUT" {
    158     const allocator = std.testing.allocator;
    159     var data = [_]u16{ 3, 10, 5, 1 };
    160     const bitmap = (try generateBitmap(&data, allocator)).bitmap;
    161     defer allocator.free(bitmap);
    162     const lut = (try generateForwardLUT(bitmap, allocator)).lut;
    163     defer allocator.free(lut);
    164     applyLUT(lut, &data);
    165     try expect(data[0] == 2);
    166     try expect(data[1] == 4);
    167     try expect(data[2] == 3);
    168     try expect(data[3] == 1);
    169 }
    170 
    171 test "apply LUTs roundtrip" {
    172     const allocator = std.testing.allocator;
    173     var data = [_]u16{ 3, 10, 5, 1 };
    174     const bitmap = (try generateBitmap(&data, allocator)).bitmap;
    175     defer allocator.free(bitmap);
    176     const forward_lut = (try generateForwardLUT(bitmap, allocator)).lut;
    177     defer allocator.free(forward_lut);
    178     const reverse_lut = (try generateReverseLUT(bitmap, allocator)).lut;
    179     defer allocator.free(reverse_lut);
    180     applyLUT(forward_lut, &data);
    181     applyLUT(reverse_lut, &data);
    182     try expect(data[0] == 3);
    183     try expect(data[1] == 10);
    184     try expect(data[2] == 5);
    185     try expect(data[3] == 1);
    186 }