From 853ec2274934be73239aa97238cda323cf0289f5 Mon Sep 17 00:00:00 2001 From: Marcin Kurczewski Date: Mon, 8 Dec 2014 11:07:44 +0100 Subject: [PATCH 01/17] Extracted LzssCompressor for Ruby version --- src-ruby/lib/lzss_compression_state.rb | 13 ++++++ src-ruby/lib/lzss_compressor.rb | 45 ++++++++++++++++++ src-ruby/lib/tlg5_reader.rb | 63 +++----------------------- 3 files changed, 65 insertions(+), 56 deletions(-) create mode 100644 src-ruby/lib/lzss_compression_state.rb create mode 100644 src-ruby/lib/lzss_compressor.rb diff --git a/src-ruby/lib/lzss_compression_state.rb b/src-ruby/lib/lzss_compression_state.rb new file mode 100644 index 0000000..c653675 --- /dev/null +++ b/src-ruby/lib/lzss_compression_state.rb @@ -0,0 +1,13 @@ +# Holds TLG compression state. +class LzssCompressorState + # dictionary used by the modified LZW compression algorithm + attr_reader :text + + # offset within the dictionary + attr_accessor :offset + + def initialize + @text = Array(0..4095).fill(0) + @offset = 0 + end +end diff --git a/src-ruby/lib/lzss_compressor.rb b/src-ruby/lib/lzss_compressor.rb new file mode 100644 index 0000000..e968168 --- /dev/null +++ b/src-ruby/lib/lzss_compressor.rb @@ -0,0 +1,45 @@ +# LZSS-based compressor used by TLG5 and TLG6. +class LzssCompressor + def self.decompress(compressor_state, input_data, input_size) + output_data = [] + flags = 0 + i = 0 + while i < input_size + flags >>= 1 + if (flags & 0x100) != 0x100 + flags = input_data[i] | 0xff00 + i += 1 + end + + if (flags & 1) == 1 + x0, x1 = input_data[i], input_data[i + 1] + i += 2 + position = x0 | ((x1 & 0xf) << 8) + length = 3 + ((x1 & 0xf0) >> 4) + if length == 18 + length += input_data[i] + i += 1 + end + j = 0 + while j < length + c = compressor_state.text[position] + output_data << c + compressor_state.text[compressor_state.offset] = c + compressor_state.offset += 1 + compressor_state.offset &= 0xfff + position += 1 + position &= 0xfff + j += 1 + end + else + c = input_data[i] + i += 1 + output_data << c + compressor_state.text[compressor_state.offset] = c + compressor_state.offset += 1 + compressor_state.offset &= 0xfff + end + end + output_data + end +end diff --git a/src-ruby/lib/tlg5_reader.rb b/src-ruby/lib/tlg5_reader.rb index 5b45fa0..579e616 100644 --- a/src-ruby/lib/tlg5_reader.rb +++ b/src-ruby/lib/tlg5_reader.rb @@ -1,5 +1,7 @@ require_relative 'image' require_relative 'tlg_reader' +require_relative 'lzss_compressor' +require_relative 'lzss_compression_state' require 'stringio' # A TLG reader that handles TLG version 5. @@ -33,7 +35,7 @@ def skip_block_information(file) end def read_pixels(file) - @compressor_state = Tlg5CompressorState.new + @compressor_state = LzssCompressorState.new pixels = [] (0..@header.image_height - 1).step(@header.block_height) do |block_y| channel_data = read_channel_data(file) @@ -104,47 +106,10 @@ def read_channel_data(file) end def decompress_block(block_info) - input_data = block_info.block_data - output_data = [] - flags = 0 - i = 0 - while i < block_info.block_size - flags >>= 1 - if (flags & 0x100) != 0x100 - flags = input_data[i] | 0xff00 - i += 1 - end - - if (flags & 1) == 1 - x0, x1 = input_data[i], input_data[i + 1] - i += 2 - position = x0 | ((x1 & 0xf) << 8) - length = 3 + ((x1 & 0xf0) >> 4) - if length == 18 - length += input_data[i] - i += 1 - end - j = 0 - while j < length - c = @compressor_state.text[position] - output_data << c - @compressor_state.text[@compressor_state.offset] = c - @compressor_state.offset += 1 - @compressor_state.offset &= 0xfff - position += 1 - position &= 0xfff - j += 1 - end - else - c = input_data[i] - i += 1 - output_data << c - @compressor_state.text[@compressor_state.offset] = c - @compressor_state.offset += 1 - @compressor_state.offset &= 0xfff - end - end - block_info.block_data = output_data + block_info.block_data = LzssCompressor.decompress( + @compressor_state, + block_info.block_data, + block_info.block_size) end # A block information. @@ -179,18 +144,4 @@ def initialize(file) @block_height = file.read(4).unpack(' Date: Mon, 8 Dec 2014 11:31:11 +0100 Subject: [PATCH 02/17] Extracted LzssCompressor for C++ version --- src/LzssCompressionState.cc | 7 ++++ src/LzssCompressionState.h | 13 +++++++ src/LzssCompressor.cc | 49 +++++++++++++++++++++++++ src/LzssCompressor.h | 17 +++++++++ src/Tlg5Reader.cc | 72 +++++++------------------------------ 5 files changed, 98 insertions(+), 60 deletions(-) create mode 100644 src/LzssCompressionState.cc create mode 100644 src/LzssCompressionState.h create mode 100644 src/LzssCompressor.cc create mode 100644 src/LzssCompressor.h diff --git a/src/LzssCompressionState.cc b/src/LzssCompressionState.cc new file mode 100644 index 0000000..54b473b --- /dev/null +++ b/src/LzssCompressionState.cc @@ -0,0 +1,7 @@ +#include "LzssCompressionState.h" + +LzssCompressionState::LzssCompressionState() +{ + for (int i = 0; i < 4096; i ++) + text[i] = 0; +} diff --git a/src/LzssCompressionState.h b/src/LzssCompressionState.h new file mode 100644 index 0000000..e142f89 --- /dev/null +++ b/src/LzssCompressionState.h @@ -0,0 +1,13 @@ +#ifndef LZSS_COMPRESSION_STATE_H_ +#define LZSS_COMPRESSION_STATE_H_ +#include + +struct LzssCompressionState +{ + uint8_t text[4096]; + uint16_t offset = 0; + + LzssCompressionState(); +}; + +#endif diff --git a/src/LzssCompressor.cc b/src/LzssCompressor.cc new file mode 100644 index 0000000..99beb5e --- /dev/null +++ b/src/LzssCompressor.cc @@ -0,0 +1,49 @@ +#include "LzssCompressor.h" + +void LzssCompressor::decompress( + LzssCompressionState &compression_state, + uint8_t *input, + size_t input_size, + uint8_t *output) +{ + uint8_t *end = input + input_size; + + for (uint32_t i = 0; i < input_size; i ++) + output[i] = 0; + + int flags = 0; + while (input < end) + { + flags >>= 1; + if ((flags & 0x100) != 0x100) + flags = *input++ | 0xff00; + + if ((flags & 1) == 1) + { + uint8_t x0 = *input++; + uint8_t x1 = *input++; + int position = x0 | ((x1 & 0xf) << 8); + int length = 3 + ((x1 & 0xf0) >> 4); + if (length == 18) + length += *input++; + for (int j = 0; j < length; j ++) + { + uint8_t c = compression_state.text[position]; + *output ++ = c; + compression_state.text[compression_state.offset] = c; + compression_state.offset ++; + compression_state.offset &= 0xfff; + position ++; + position &= 0xfff; + } + } + else + { + uint8_t c = *input ++; + *output ++ = c; + compression_state.text[compression_state.offset] = c; + compression_state.offset ++; + compression_state.offset &= 0xfff; + } + } +} diff --git a/src/LzssCompressor.h b/src/LzssCompressor.h new file mode 100644 index 0000000..38b98b0 --- /dev/null +++ b/src/LzssCompressor.h @@ -0,0 +1,17 @@ +#ifndef LZSS_COMPRESSOR_H_ +#define LZSS_COMPRESSOR_H_ +#include +#include +#include "LzssCompressionState.h" + +class LzssCompressor +{ + public: + static void decompress( + LzssCompressionState &compression_state, + uint8_t *input, + size_t input_size, + uint8_t *output); +}; + +#endif diff --git a/src/Tlg5Reader.cc b/src/Tlg5Reader.cc index ec8b217..caf40a8 100644 --- a/src/Tlg5Reader.cc +++ b/src/Tlg5Reader.cc @@ -2,6 +2,8 @@ #include #include #include +#include "LzssCompressionState.h" +#include "LzssCompressor.h" #include "Tlg5Reader.h" namespace @@ -23,20 +25,6 @@ namespace ifs.read((char*) block_data.get(), block_size); } - struct Tlg5CompressionState - { - uint8_t text[4096]; - uint16_t offset = 0; - - Tlg5CompressionState(); - }; - - Tlg5CompressionState::Tlg5CompressionState() - { - for (int i = 0; i < 4096; i ++) - text[i] = 0; - } - struct Tlg5Header { uint8_t channel_count; @@ -57,59 +45,23 @@ namespace void decompress( const Tlg5Header &header, - Tlg5CompressionState &compression_state, + LzssCompressionState &compression_state, Tlg5BlockInfo &block_info) { - auto size = header.block_height * header.image_width; - auto new_data = std::unique_ptr(new uint8_t[size]); - uint8_t *output = new_data.get(); - uint8_t *input = block_info.block_data.get(); - uint8_t *end = input + block_info.block_size; - - for (uint32_t i = 0; i < size; i ++) - output[i] = 0; + auto new_data = new uint8_t[header.image_width * header.block_height]; - int flags = 0; - while (input < end) - { - flags >>= 1; - if ((flags & 0x100) != 0x100) - flags = *input++ | 0xff00; + LzssCompressor::decompress( + compression_state, + block_info.block_data.get(), + block_info.block_size, + new_data); - if ((flags & 1) == 1) - { - uint8_t x0 = *input++; - uint8_t x1 = *input++; - int position = x0 | ((x1 & 0xf) << 8); - int length = 3 + ((x1 & 0xf0) >> 4); - if (length == 18) - length += *input++; - for (int j = 0; j < length; j ++) - { - uint8_t c = compression_state.text[position]; - *output ++ = c; - compression_state.text[compression_state.offset] = c; - compression_state.offset ++; - compression_state.offset &= 0xfff; - position ++; - position &= 0xfff; - } - } - else - { - uint8_t c = *input ++; - *output ++ = c; - compression_state.text[compression_state.offset] = c; - compression_state.offset ++; - compression_state.offset &= 0xfff; - } - } - block_info.block_data = std::move(new_data); + block_info.block_data = std::unique_ptr(new_data); } std::map read_channel_data( const Tlg5Header &header, - Tlg5CompressionState &compression_state, + LzssCompressionState &compression_state, std::ifstream &ifs) { std::map map; @@ -196,7 +148,7 @@ namespace void read_pixels(const Tlg5Header &header, std::ifstream &ifs, uint32_t *pixels) { - Tlg5CompressionState state; + LzssCompressionState state; for (uint32_t y = 0; y < header.image_height; y += header.block_height) { auto channel_data = read_channel_data(header, state, ifs); From 5963d355e77c4ce49c16105b2811c08d7b12b62d Mon Sep 17 00:00:00 2001 From: Marcin Kurczewski Date: Mon, 8 Dec 2014 11:33:40 +0100 Subject: [PATCH 03/17] Fixup ruby lzss --- src-ruby/lib/lzss_compression_state.rb | 2 +- src-ruby/lib/tlg5_reader.rb | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src-ruby/lib/lzss_compression_state.rb b/src-ruby/lib/lzss_compression_state.rb index c653675..c35eaf6 100644 --- a/src-ruby/lib/lzss_compression_state.rb +++ b/src-ruby/lib/lzss_compression_state.rb @@ -1,5 +1,5 @@ # Holds TLG compression state. -class LzssCompressorState +class LzssCompressionState # dictionary used by the modified LZW compression algorithm attr_reader :text diff --git a/src-ruby/lib/tlg5_reader.rb b/src-ruby/lib/tlg5_reader.rb index 579e616..660b071 100644 --- a/src-ruby/lib/tlg5_reader.rb +++ b/src-ruby/lib/tlg5_reader.rb @@ -35,7 +35,7 @@ def skip_block_information(file) end def read_pixels(file) - @compressor_state = LzssCompressorState.new + @compression_state = LzssCompressionState.new pixels = [] (0..@header.image_height - 1).step(@header.block_height) do |block_y| channel_data = read_channel_data(file) @@ -107,7 +107,7 @@ def read_channel_data(file) def decompress_block(block_info) block_info.block_data = LzssCompressor.decompress( - @compressor_state, + @compression_state, block_info.block_data, block_info.block_size) end From bec7cd99fbb1252f7331dcbf91f23c80ce27b10a Mon Sep 17 00:00:00 2001 From: Marcin Kurczewski Date: Mon, 8 Dec 2014 12:03:40 +0100 Subject: [PATCH 04/17] Added TLG6 filter types decoding for Ruby version --- src-ruby/lib/tlg6_reader.rb | 52 +++++++++++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/src-ruby/lib/tlg6_reader.rb b/src-ruby/lib/tlg6_reader.rb index 5162bc2..8692c81 100644 --- a/src-ruby/lib/tlg6_reader.rb +++ b/src-ruby/lib/tlg6_reader.rb @@ -1,11 +1,63 @@ require_relative 'tlg_reader' +require_relative 'lzss_compression_state' # A TLG reader that handles TLG version 6. class Tlg6Reader < TlgReader MAGIC = "\x54\x4c\x47\x36\x2e\x30\x00\x72\x61\x77\x1a" + TLG6_W_BLOCK_SIZE = 8 + TLG6_H_BLOCK_SIZE = 8 def read_stream(file) fail 'Not a TLG6 file ' if file.read(11) != MAGIC + @header = Tlg6Header.new(file) + unless [1, 3, 4].include?(@header.channel_count) + fail 'Unsupported channel count: ' + @header.channel_count.to_s + end + + x_block_count = ((@header.image_width - 1) / TLG6_W_BLOCK_SIZE) + 1; + y_block_count = ((@header.image_height - 1) / TLG6_H_BLOCK_SIZE) + 1; + filter_types = read_filter_types(file) + fail 'Not implemented! Please send sample files to @rr- on github.' end + + private + + def read_filter_types(file) + compression_state = LzssCompressionState.new + + z = 0 + (0..31).each do |i| + (0..15).each do |j| + (0..3).each { compression_state.text[z += 1] = i } + (0..3).each { compression_state.text[z += 1] = j } + end + end + + data_size = file.read(4).unpack(' Date: Mon, 8 Dec 2014 21:09:00 +0100 Subject: [PATCH 05/17] Added TLG6 filter types decoding for C++ version --- src/Tlg6Reader.cc | 95 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 95 insertions(+) diff --git a/src/Tlg6Reader.cc b/src/Tlg6Reader.cc index cbfb7f3..a580db5 100644 --- a/src/Tlg6Reader.cc +++ b/src/Tlg6Reader.cc @@ -1,6 +1,88 @@ +#include #include +#include "LzssCompressionState.h" +#include "LzssCompressor.h" #include "Tlg6Reader.h" +#define TLG6_W_BLOCK_SIZE 8 +#define TLG6_H_BLOCK_SIZE 8 + +namespace +{ + struct Tlg6Header + { + uint8_t channel_count; + uint8_t data_flags; + uint8_t color_type; + uint8_t external_golomb_table; + uint32_t image_width; + uint32_t image_height; + uint32_t max_bit_length; + uint32_t x_block_count; + uint32_t y_block_count; + + void read(std::ifstream &ifs); + }; + + void Tlg6Header::read(std::ifstream &ifs) + { + ifs.read((char*) &channel_count, 1); + ifs.read((char*) &data_flags, 1); + ifs.read((char*) &color_type, 1); + ifs.read((char*) &external_golomb_table, 1); + ifs.read((char*) &image_width, 4); + ifs.read((char*) &image_height, 4); + ifs.read((char*) &max_bit_length, 4); + + x_block_count = ((image_width - 1)/ TLG6_W_BLOCK_SIZE) + 1; + y_block_count = ((image_height - 1)/ TLG6_H_BLOCK_SIZE) + 1; + } + + struct Tlg6FilterTypes + { + uint32_t data_size; + std::unique_ptr data = nullptr; + + void read(std::ifstream &ifs); + void decompress(Tlg6Header const &header); + }; + + void Tlg6FilterTypes::read(std::ifstream &ifs) + { + ifs.read((char*) &data_size, 4); + data = std::unique_ptr(new uint8_t[data_size]); + ifs.read((char*) data.get(), data_size); + } + + void Tlg6FilterTypes::decompress(Tlg6Header const &header) + { + auto output_size = header.x_block_count * header.y_block_count; + uint8_t *output = new uint8_t[output_size]; + + LzssCompressionState compression_state; + uint8_t *ptr = compression_state.text; + for (int i = 0; i < 32; i ++) + { + for (int j = 0; j < 16; j ++) + { + for (int k = 0; k < 4; k ++) + *ptr ++ = i; + + for (int k = 0; k < 4; k ++) + *ptr ++ = j; + } + } + + LzssCompressor::decompress( + compression_state, + data.get(), + data_size, + output); + + data = std::unique_ptr(output); + } +} + const std::string Tlg6Reader::get_magic() const { return std::string("\x54\x4c\x47\x36\x2e\x30\x00\x72\x61\x77\x1a", 11); @@ -8,6 +90,19 @@ const std::string Tlg6Reader::get_magic() const const Image Tlg6Reader::read_raw_data(std::ifstream &ifs) const { + Tlg6Header header; + header.read(ifs); + if (header.channel_count != 1 + && header.channel_count != 3 + && header.channel_count != 4) + { + throw std::runtime_error("Unsupported channel count"); + } + + Tlg6FilterTypes filter_types; + filter_types.read(ifs); + filter_types.decompress(header); + throw std::runtime_error( "Not implemented! Please send sample files to rr- on github."); } From 7161ececf205ad4d5e0c8ca134a5a1a2a786fed3 Mon Sep 17 00:00:00 2001 From: Marcin Kurczewski Date: Mon, 8 Dec 2014 21:15:37 +0100 Subject: [PATCH 06/17] Changed decompression function placement --- src-ruby/lib/tlg5_reader.rb | 16 ++++++------ src-ruby/lib/tlg6_reader.rb | 45 ++++++++++++++++++++------------- src/Tlg5Reader.cc | 50 +++++++++++++++++++------------------ 3 files changed, 62 insertions(+), 49 deletions(-) diff --git a/src-ruby/lib/tlg5_reader.rb b/src-ruby/lib/tlg5_reader.rb index 660b071..b72773e 100644 --- a/src-ruby/lib/tlg5_reader.rb +++ b/src-ruby/lib/tlg5_reader.rb @@ -99,19 +99,12 @@ def read_channel_data(file) channel_data = {} (0..@header.channel_count - 1).each do |channel| block_info = Tlg5BlockInfo.new(file) - decompress_block(block_info) unless block_info.mark + block_info.decompress(@compression_state) unless block_info.mark channel_data[channel] = block_info.block_data end channel_data end - def decompress_block(block_info) - block_info.block_data = LzssCompressor.decompress( - @compression_state, - block_info.block_data, - block_info.block_size) - end - # A block information. class Tlg5BlockInfo # Is decompressed? @@ -128,6 +121,13 @@ def initialize(file) @block_size = file.read(4).unpack(' block_data = nullptr; - - void read(std::ifstream &ifs); - }; - - void Tlg5BlockInfo::read(std::ifstream &ifs) - { - ifs.read((char*) &mark, 1); - ifs.read((char*) &block_size, 4); - block_data = std::unique_ptr(new uint8_t[block_size]); - ifs.read((char*) block_data.get(), block_size); - } - struct Tlg5Header { uint8_t channel_count; @@ -43,20 +26,39 @@ namespace ifs.read((char*) &block_height, 4); } - void decompress( + struct Tlg5BlockInfo + { + uint8_t mark; + uint32_t block_size; + std::unique_ptr block_data = nullptr; + + void read(std::ifstream &ifs); + void decompress( + const Tlg5Header &header, + LzssCompressionState &compression_state); + }; + + void Tlg5BlockInfo::read(std::ifstream &ifs) + { + ifs.read((char*) &mark, 1); + ifs.read((char*) &block_size, 4); + block_data = std::unique_ptr(new uint8_t[block_size]); + ifs.read((char*) block_data.get(), block_size); + } + + void Tlg5BlockInfo::decompress( const Tlg5Header &header, - LzssCompressionState &compression_state, - Tlg5BlockInfo &block_info) + LzssCompressionState &compression_state) { auto new_data = new uint8_t[header.image_width * header.block_height]; LzssCompressor::decompress( compression_state, - block_info.block_data.get(), - block_info.block_size, + block_data.get(), + block_size, new_data); - block_info.block_data = std::unique_ptr(new_data); + block_data = std::unique_ptr(new_data); } std::map read_channel_data( @@ -71,7 +73,7 @@ namespace block_info.read(ifs); if (!block_info.mark) { - decompress(header, compression_state, block_info); + block_info.decompress(header, compression_state); } map[channel] = std::move(block_info); } From d20bec2e4a4eb66442478578d490100c4a4abe5a Mon Sep 17 00:00:00 2001 From: Marcin Kurczewski Date: Mon, 8 Dec 2014 22:40:06 +0100 Subject: [PATCH 07/17] Added TLG6 reading for C++ version Ported from krkrz with minor changes. --- Makefile | 2 +- src/Tlg6Reader.cc | 438 +++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 437 insertions(+), 3 deletions(-) diff --git a/Makefile b/Makefile index 0e02c6b..cddaf84 100644 --- a/Makefile +++ b/Makefile @@ -5,7 +5,7 @@ EXECUTABLE := tlg2png SOURCES := $(wildcard $(SRCDIR)*.cc) OBJECTS := $(SOURCES:$(SRCDIR)%.cc=$(OBJDIR)%.o) -CXXFLAGS := -O3 -Wall -pedantic -std=c++11 +CXXFLAGS := -Wall -pedantic -std=c++11 -g LFLAGS := -lpng all: $(EXECUTABLE) diff --git a/src/Tlg6Reader.cc b/src/Tlg6Reader.cc index a580db5..b68305c 100644 --- a/src/Tlg6Reader.cc +++ b/src/Tlg6Reader.cc @@ -1,3 +1,4 @@ +#include //! #include #include #include "LzssCompressionState.h" @@ -81,6 +82,331 @@ namespace data = std::unique_ptr(output); } + + /* !!!!! */ + + uint32_t make_gt_mask(uint32_t a, uint32_t b) + { + uint32_t tmp2 = ~b; + uint32_t tmp = ((a & tmp2) + (((a ^ tmp2) >> 1) & 0x7f7f7f7f)) & 0x80808080; + tmp = ((tmp >> 7) + 0x7f7f7f7f) ^ 0x7f7f7f7f; + return tmp; + } + + uint32_t packed_bytes_add(uint32_t a, uint32_t b) + { + uint32_t tmp = (((a & b) << 1) + ((a ^ b) & 0xfefefefe)) & 0x01010100; + return a + b - tmp; + } + + uint32_t med2(uint32_t a, uint32_t b, uint32_t c) + { + uint32_t aa_gt_bb = make_gt_mask(a, b); + uint32_t a_xor_b_and_aa_gt_bb = ((a ^ b) & aa_gt_bb); + uint32_t aa = a_xor_b_and_aa_gt_bb ^ a; + uint32_t bb = a_xor_b_and_aa_gt_bb ^ b; + uint32_t n = make_gt_mask(c, bb); + uint32_t nn = make_gt_mask(aa, c); + uint32_t m = ~(n | nn); + return (n & aa) | (nn & bb) | ((bb & m) - (c & m) + (aa & m)); + } + + uint32_t med(uint32_t a, uint32_t b, uint32_t c, uint32_t v) + { + return packed_bytes_add(med2(a, b, c), v); + } + + #define TLG6_AVG_PACKED(x, y) ((((x) & (y)) + ((((x) ^ (y)) & 0xfefefefe) >> 1)) + (((x)^(y))&0x01010101)) + + uint32_t avg(uint32_t a, uint32_t b, uint32_t c, uint32_t v) + { + return packed_bytes_add(TLG6_AVG_PACKED(a, b), v); + } + + #define TLG6_GOLOMB_HALF_THRESHOLD 8 + #define TLG6_GOLOMB_N_COUNT 4 + #define TLG6_LeadingZeroTable_BITS 12 + #define TLG6_LeadingZeroTable_SIZE (1<> bit_pos; + int b = TLG6LeadingZeroTable[t&(TLG6_LeadingZeroTable_SIZE - 1)]; + int bit_count = b; + while (!b) + { + bit_count += TLG6_LeadingZeroTable_BITS; + bit_pos += TLG6_LeadingZeroTable_BITS; + bit_pool += bit_pos >> 3; + bit_pos &= 7; + t = TLG6_FETCH_32BITS(bit_pool) >> bit_pos; + b = TLG6LeadingZeroTable[t&(TLG6_LeadingZeroTable_SIZE - 1)]; + bit_count += b; + } + + bit_pos += b; + bit_pool += bit_pos >> 3; + bit_pos &= 7; + + bit_count --; + count = 1 << bit_count; + count += ((TLG6_FETCH_32BITS(bit_pool) >> (bit_pos)) & (count - 1)); + + bit_pos += bit_count; + bit_pool += bit_pos >> 3; + bit_pos &= 7; + } + + if (zero) + { + do + { + *pixel_buf = 0; + pixel_buf += 4; + } + while (-- count); + + zero ^= 1; + } + else + { + do + { + int k = TLG6GolombBitLengthTable[a][n], v, sign; + + uint32_t t = TLG6_FETCH_32BITS(bit_pool) >> bit_pos; + int bit_count; + int b; + if (t) + { + b = TLG6LeadingZeroTable[t&(TLG6_LeadingZeroTable_SIZE - 1)]; + bit_count = b; + while (!b) + { + bit_count += TLG6_LeadingZeroTable_BITS; + bit_pos += TLG6_LeadingZeroTable_BITS; + bit_pool += bit_pos >> 3; + bit_pos &= 7; + t = TLG6_FETCH_32BITS(bit_pool) >> bit_pos; + b = TLG6LeadingZeroTable[t&(TLG6_LeadingZeroTable_SIZE - 1)]; + bit_count += b; + } + bit_count --; + } + else + { + bit_pool += 5; + bit_count = bit_pool[-1]; + bit_pos = 0; + t = TLG6_FETCH_32BITS(bit_pool); + b = 0; + } + + v = (bit_count << k) + ((t >> b) & ((1 << k) - 1)); + sign = (v & 1) - 1; + v >>= 1; + a += v; + + if (channel == 0) + *(uint32_t*)pixel_buf = ((v ^ sign) + sign + 1); + else + *(int8_t*)pixel_buf = ((v ^ sign) + sign + 1); + + pixel_buf += 4; + + bit_pos += b; + bit_pos += k; + bit_pool += bit_pos >> 3; + bit_pos &= 7; + + if (-- n < 0) + { + a >>= 1; + n = TLG6_GOLOMB_N_COUNT - 1; + } + } + while (-- count); + + zero ^= 1; + } + } + } + + void decode_line( + uint32_t *prev_line, + uint32_t *current_line, + int width, + int start_block, + int block_limit, + uint8_t *filtertypes, + int skip_block_bytes, + uint32_t *in, + uint32_t initialp, + int odd_skip, + int dir) + { + uint32_t p, up; + int step, i; + + if (start_block) + { + prev_line += start_block * TLG6_W_BLOCK_SIZE; + current_line += start_block * TLG6_W_BLOCK_SIZE; + p = current_line[-1]; + up = prev_line[-1]; + } + else + { + p = up = initialp; + } + + in += skip_block_bytes * start_block; + step = (dir & 1) ? 1 : -1; + + for (i = start_block; i < block_limit; i ++) + { + int w = width - i * TLG6_W_BLOCK_SIZE; + if (w > TLG6_W_BLOCK_SIZE) + w = TLG6_W_BLOCK_SIZE; + + int ww = w; + if (step == -1) + in += ww - 1; + + if (i & 1) + in += odd_skip * ww; + + switch (filtertypes[i]) + { + #define IA (char)((*in>>24)&0xff) + #define IR (char)((*in>>16)&0xff) + #define IG (char)((*in>>8)&0xff) + #define IB (char)((*in)&0xff) + TLG6_DO_CHROMA_DECODE( 0, IB, IG, IR); + TLG6_DO_CHROMA_DECODE( 1, IB+IG, IG, IR+IG); + TLG6_DO_CHROMA_DECODE( 2, IB, IG+IB, IR+IB+IG); + TLG6_DO_CHROMA_DECODE( 3, IB+IR+IG, IG+IR, IR); + TLG6_DO_CHROMA_DECODE( 4, IB+IR, IG+IB+IR, IR+IB+IR+IG); + TLG6_DO_CHROMA_DECODE( 5, IB+IR, IG+IB+IR, IR); + TLG6_DO_CHROMA_DECODE( 6, IB+IG, IG, IR); + TLG6_DO_CHROMA_DECODE( 7, IB, IG+IB, IR); + TLG6_DO_CHROMA_DECODE( 8, IB, IG, IR+IG); + TLG6_DO_CHROMA_DECODE( 9, IB+IG+IR+IB, IG+IR+IB, IR+IB); + TLG6_DO_CHROMA_DECODE(10, IB+IR, IG+IR, IR); + TLG6_DO_CHROMA_DECODE(11, IB, IG+IB, IR+IB); + TLG6_DO_CHROMA_DECODE(12, IB, IG+IR+IB, IR+IB); + TLG6_DO_CHROMA_DECODE(13, IB+IG, IG+IR+IB+IG, IR+IB+IG); + TLG6_DO_CHROMA_DECODE(14, IB+IG+IR, IG+IR, IR+IB+IG+IR); + TLG6_DO_CHROMA_DECODE(15, IB, IG+(IB<<1), IR+(IB<<1)); + + default: return; + } + + if (step == 1) + in += skip_block_bytes - ww; + else + in += skip_block_bytes + 1; + + if (i&1) + in -= odd_skip * ww; + + #undef IR + #undef IG + #undef IB + } + } + + /* ! */ } const std::string Tlg6Reader::get_magic() const @@ -103,6 +429,114 @@ const Image Tlg6Reader::read_raw_data(std::ifstream &ifs) const filter_types.read(ifs); filter_types.decompress(header); - throw std::runtime_error( - "Not implemented! Please send sample files to rr- on github."); + Image image; + image.width = header.image_width; + image.height = header.image_height; + image.pixels = new uint32_t[image.width * image.height]; + + uint32_t *pixel_buf = new uint32_t[4 * header.image_width * TLG6_H_BLOCK_SIZE]; + uint32_t *zeroline = new uint32_t[header.image_width]; + uint32_t *prev_line = zeroline; + for (uint32_t i = 0; i < header.image_width; i ++) + zeroline[i] = 0; + + /* ! */ + + InitTLG6Table(); + uint32_t main_count = header.image_width / TLG6_W_BLOCK_SIZE; + int fraction = header.image_width - main_count * TLG6_W_BLOCK_SIZE; + for (uint32_t y = 0; y < header.image_height; y += TLG6_H_BLOCK_SIZE) + { + uint32_t ylim = y + TLG6_H_BLOCK_SIZE; + if (ylim >= header.image_height) + ylim = header.image_height; + + int pixel_count = (ylim - y) * header.image_width; + for (int c = 0; c < header.channel_count; c ++) + { + uint32_t bit_length; + ifs.read((char*) &bit_length, 4); + + int method = (bit_length >> 30) & 3; + bit_length &= 0x3fffffff; + + int byte_length = bit_length / 8; + if (bit_length % 8) + byte_length ++; + + uint8_t *bit_pool = new uint8_t[byte_length]; + ifs.read((char*) bit_pool, byte_length); + + if (method == 0) + { + TLG6DecodeGolombValues((uint8_t*)pixel_buf + c, pixel_count, bit_pool, c); + } + else + { + throw std::runtime_error("Unsupported encoding method"); + } + } + + uint8_t *ft = filter_types.data.get() + (y / TLG6_H_BLOCK_SIZE) * header.x_block_count; + int skip_bytes = (ylim - y) * TLG6_W_BLOCK_SIZE; + + for (uint32_t yy = y; yy < ylim; yy ++) + { + uint32_t *current_line = &image.pixels[yy * image.width]; + + int dir = (yy&1)^1; + int odd_skip = ((ylim - yy -1) - (yy - y)); + + if (main_count) + { + int start = ((header.image_width < TLG6_W_BLOCK_SIZE) + ? header.image_width + : TLG6_W_BLOCK_SIZE) * (yy - y); + + decode_line( + prev_line, + current_line, + header.image_width, + 0, + main_count, + ft, + skip_bytes, + pixel_buf + start, + header.channel_count == 3 + ? 0xff000000 + : 0, + odd_skip, + dir); + } + + if (main_count != header.x_block_count) + { + int ww = fraction; + if (ww > TLG6_W_BLOCK_SIZE) + ww = TLG6_W_BLOCK_SIZE; + + int start = ww * (yy - y); + decode_line( + prev_line, + current_line, + header.image_width, + main_count, + header.x_block_count, + ft, + skip_bytes, + pixel_buf + start, + header.channel_count == 3 + ? 0xff000000 + : 0, + odd_skip, + dir); + } + + prev_line = current_line; + } + } + + /* ! */ + + return image; } From 61d7a9609a17d9f837fc2edcf2d7516befc1750d Mon Sep 17 00:00:00 2001 From: Marcin Kurczewski Date: Tue, 9 Dec 2014 11:27:38 +0100 Subject: [PATCH 08/17] Refactored C++ TLG6 reading --- src/Tlg6Reader.cc | 292 +++++++++++++++++++++++----------------------- 1 file changed, 147 insertions(+), 145 deletions(-) diff --git a/src/Tlg6Reader.cc b/src/Tlg6Reader.cc index b68305c..2d8eda2 100644 --- a/src/Tlg6Reader.cc +++ b/src/Tlg6Reader.cc @@ -1,15 +1,22 @@ #include //! +#include //! #include #include #include "LzssCompressionState.h" #include "LzssCompressor.h" #include "Tlg6Reader.h" -#define TLG6_W_BLOCK_SIZE 8 -#define TLG6_H_BLOCK_SIZE 8 - namespace { + const int w_block_size = 8; + const int h_block_size = 8; + const int golomb_n_count = 4; + const int leading_zero_table_bits = 12; + const int leading_zero_table_size = 1 << leading_zero_table_bits; + + uint8_t leading_zero_table[leading_zero_table_size]; + uint8_t golomb_bit_length_table[golomb_n_count * 2 * 128][golomb_n_count]; + struct Tlg6Header { uint8_t channel_count; @@ -35,8 +42,8 @@ namespace ifs.read((char*) &image_height, 4); ifs.read((char*) &max_bit_length, 4); - x_block_count = ((image_width - 1)/ TLG6_W_BLOCK_SIZE) + 1; - y_block_count = ((image_height - 1)/ TLG6_H_BLOCK_SIZE) + 1; + x_block_count = ((image_width - 1)/ w_block_size) + 1; + y_block_count = ((image_height - 1)/ h_block_size) + 1; } struct Tlg6FilterTypes @@ -83,23 +90,29 @@ namespace data = std::unique_ptr(output); } - /* !!!!! */ - - uint32_t make_gt_mask(uint32_t a, uint32_t b) + inline uint32_t make_gt_mask( + uint32_t const &a, + uint32_t const &b) { uint32_t tmp2 = ~b; - uint32_t tmp = ((a & tmp2) + (((a ^ tmp2) >> 1) & 0x7f7f7f7f)) & 0x80808080; - tmp = ((tmp >> 7) + 0x7f7f7f7f) ^ 0x7f7f7f7f; - return tmp; + uint32_t tmp = + ((a & tmp2) + (((a ^ tmp2) >> 1) & 0x7f7f7f7f)) & 0x80808080; + + return ((tmp >> 7) + 0x7f7f7f7f) ^ 0x7f7f7f7f; } - uint32_t packed_bytes_add(uint32_t a, uint32_t b) + inline uint32_t packed_bytes_add( + uint32_t const &a, + uint32_t const &b) { - uint32_t tmp = (((a & b) << 1) + ((a ^ b) & 0xfefefefe)) & 0x01010100; - return a + b - tmp; + return a + b - ((((a & b) << 1) + ((a ^ b) & 0xfefefefe)) & 0x01010100); } - uint32_t med2(uint32_t a, uint32_t b, uint32_t c) + inline uint32_t med( + uint32_t const &a, + uint32_t const &b, + uint32_t const &c, + uint32_t const &v) { uint32_t aa_gt_bb = make_gt_mask(a, b); uint32_t a_xor_b_and_aa_gt_bb = ((a ^ b) & aa_gt_bb); @@ -108,100 +121,94 @@ namespace uint32_t n = make_gt_mask(c, bb); uint32_t nn = make_gt_mask(aa, c); uint32_t m = ~(n | nn); - return (n & aa) | (nn & bb) | ((bb & m) - (c & m) + (aa & m)); + return packed_bytes_add((n & aa) | (nn & bb) | ((bb & m) - (c & m) + (aa & m)), v); } - uint32_t med(uint32_t a, uint32_t b, uint32_t c, uint32_t v) + inline uint32_t avg( + uint32_t const &a, + uint32_t const &b, + uint32_t const &c, + uint32_t const &v) { - return packed_bytes_add(med2(a, b, c), v); + return packed_bytes_add((a & b) + + (((a ^ b) & 0xfefefefe) >> 1) + + ((a ^ b) & 0x01010101), v); } - #define TLG6_AVG_PACKED(x, y) ((((x) & (y)) + ((((x) ^ (y)) & 0xfefefefe) >> 1)) + (((x)^(y))&0x01010101)) - - uint32_t avg(uint32_t a, uint32_t b, uint32_t c, uint32_t v) - { - return packed_bytes_add(TLG6_AVG_PACKED(a, b), v); - } - - #define TLG6_GOLOMB_HALF_THRESHOLD 8 - #define TLG6_GOLOMB_N_COUNT 4 - #define TLG6_LeadingZeroTable_BITS 12 - #define TLG6_LeadingZeroTable_SIZE (1<> bit_pos; - int b = TLG6LeadingZeroTable[t&(TLG6_LeadingZeroTable_SIZE - 1)]; + uint32_t t = *(uint32_t*)(bit_pool) >> bit_pos; + int b = leading_zero_table[t & (leading_zero_table_size - 1)]; int bit_count = b; while (!b) { - bit_count += TLG6_LeadingZeroTable_BITS; - bit_pos += TLG6_LeadingZeroTable_BITS; + bit_count += leading_zero_table_bits; + bit_pos += leading_zero_table_bits; bit_pool += bit_pos >> 3; bit_pos &= 7; - t = TLG6_FETCH_32BITS(bit_pool) >> bit_pos; - b = TLG6LeadingZeroTable[t&(TLG6_LeadingZeroTable_SIZE - 1)]; + t = *(uint32_t*)(bit_pool) >> bit_pos; + b = leading_zero_table[t & (leading_zero_table_size - 1)]; bit_count += b; } @@ -240,7 +247,7 @@ namespace bit_count --; count = 1 << bit_count; - count += ((TLG6_FETCH_32BITS(bit_pool) >> (bit_pos)) & (count - 1)); + count += ((*(uint32_t*)(bit_pool) >> (bit_pos)) & (count - 1)); bit_pos += bit_count; bit_pool += bit_pos >> 3; @@ -262,23 +269,22 @@ namespace { do { - int k = TLG6GolombBitLengthTable[a][n], v, sign; - - uint32_t t = TLG6_FETCH_32BITS(bit_pool) >> bit_pos; int bit_count; int b; + + uint32_t t = *(uint32_t*)(bit_pool) >> bit_pos; if (t) { - b = TLG6LeadingZeroTable[t&(TLG6_LeadingZeroTable_SIZE - 1)]; + b = leading_zero_table[t & (leading_zero_table_size - 1)]; bit_count = b; while (!b) { - bit_count += TLG6_LeadingZeroTable_BITS; - bit_pos += TLG6_LeadingZeroTable_BITS; + bit_count += leading_zero_table_bits; + bit_pos += leading_zero_table_bits; bit_pool += bit_pos >> 3; bit_pos &= 7; - t = TLG6_FETCH_32BITS(bit_pool) >> bit_pos; - b = TLG6LeadingZeroTable[t&(TLG6_LeadingZeroTable_SIZE - 1)]; + t = *(uint32_t*)(bit_pool) >> bit_pos; + b = leading_zero_table[t & (leading_zero_table_size - 1)]; bit_count += b; } bit_count --; @@ -288,20 +294,17 @@ namespace bit_pool += 5; bit_count = bit_pool[-1]; bit_pos = 0; - t = TLG6_FETCH_32BITS(bit_pool); + t = *(uint32_t*)(bit_pool); b = 0; } - v = (bit_count << k) + ((t >> b) & ((1 << k) - 1)); - sign = (v & 1) - 1; + int k = golomb_bit_length_table[a][n]; + int v = (bit_count << k) + ((t >> b) & ((1 << k) - 1)); + int sign = (v & 1) - 1; v >>= 1; a += v; - if (channel == 0) - *(uint32_t*)pixel_buf = ((v ^ sign) + sign + 1); - else - *(int8_t*)pixel_buf = ((v ^ sign) + sign + 1); - + *(uint8_t*)pixel_buf = ((v ^ sign) + sign + 1); pixel_buf += 4; bit_pos += b; @@ -312,7 +315,7 @@ namespace if (-- n < 0) { a >>= 1; - n = TLG6_GOLOMB_N_COUNT - 1; + n = golomb_n_count - 1; } } while (-- count); @@ -340,8 +343,8 @@ namespace if (start_block) { - prev_line += start_block * TLG6_W_BLOCK_SIZE; - current_line += start_block * TLG6_W_BLOCK_SIZE; + prev_line += start_block * w_block_size; + current_line += start_block * w_block_size; p = current_line[-1]; up = prev_line[-1]; } @@ -355,9 +358,9 @@ namespace for (i = start_block; i < block_limit; i ++) { - int w = width - i * TLG6_W_BLOCK_SIZE; - if (w > TLG6_W_BLOCK_SIZE) - w = TLG6_W_BLOCK_SIZE; + int w = width - i * w_block_size; + if (w > w_block_size) + w = w_block_size; int ww = w; if (step == -1) @@ -368,26 +371,26 @@ namespace switch (filtertypes[i]) { - #define IA (char)((*in>>24)&0xff) - #define IR (char)((*in>>16)&0xff) - #define IG (char)((*in>>8)&0xff) - #define IB (char)((*in)&0xff) - TLG6_DO_CHROMA_DECODE( 0, IB, IG, IR); - TLG6_DO_CHROMA_DECODE( 1, IB+IG, IG, IR+IG); - TLG6_DO_CHROMA_DECODE( 2, IB, IG+IB, IR+IB+IG); - TLG6_DO_CHROMA_DECODE( 3, IB+IR+IG, IG+IR, IR); - TLG6_DO_CHROMA_DECODE( 4, IB+IR, IG+IB+IR, IR+IB+IR+IG); - TLG6_DO_CHROMA_DECODE( 5, IB+IR, IG+IB+IR, IR); - TLG6_DO_CHROMA_DECODE( 6, IB+IG, IG, IR); - TLG6_DO_CHROMA_DECODE( 7, IB, IG+IB, IR); - TLG6_DO_CHROMA_DECODE( 8, IB, IG, IR+IG); - TLG6_DO_CHROMA_DECODE( 9, IB+IG+IR+IB, IG+IR+IB, IR+IB); - TLG6_DO_CHROMA_DECODE(10, IB+IR, IG+IR, IR); - TLG6_DO_CHROMA_DECODE(11, IB, IG+IB, IR+IB); - TLG6_DO_CHROMA_DECODE(12, IB, IG+IR+IB, IR+IB); - TLG6_DO_CHROMA_DECODE(13, IB+IG, IG+IR+IB+IG, IR+IB+IG); - TLG6_DO_CHROMA_DECODE(14, IB+IG+IR, IG+IR, IR+IB+IG+IR); - TLG6_DO_CHROMA_DECODE(15, IB, IG+(IB<<1), IR+(IB<<1)); + #define IA (uint8_t)((*in>>24)&0xff) + #define IR (uint8_t)((*in>>16)&0xff) + #define IG (uint8_t)((*in>>8)&0xff) + #define IB (uint8_t)((*in)&0xff) + DO_CHROMA_DECODE( 0, IB, IG, IR); + DO_CHROMA_DECODE( 1, IB+IG, IG, IR+IG); + DO_CHROMA_DECODE( 2, IB, IG+IB, IR+IB+IG); + DO_CHROMA_DECODE( 3, IB+IR+IG, IG+IR, IR); + DO_CHROMA_DECODE( 4, IB+IR, IG+IB+IR, IR+IB+IR+IG); + DO_CHROMA_DECODE( 5, IB+IR, IG+IB+IR, IR); + DO_CHROMA_DECODE( 6, IB+IG, IG, IR); + DO_CHROMA_DECODE( 7, IB, IG+IB, IR); + DO_CHROMA_DECODE( 8, IB, IG, IR+IG); + DO_CHROMA_DECODE( 9, IB+IG+IR+IB, IG+IR+IB, IR+IB); + DO_CHROMA_DECODE(10, IB+IR, IG+IR, IR); + DO_CHROMA_DECODE(11, IB, IG+IB, IR+IB); + DO_CHROMA_DECODE(12, IB, IG+IR+IB, IR+IB); + DO_CHROMA_DECODE(13, IB+IG, IG+IR+IB+IG, IR+IB+IG); + DO_CHROMA_DECODE(14, IB+IG+IR, IG+IR, IR+IB+IG+IR); + DO_CHROMA_DECODE(15, IB, IG+(IB<<1), IR+(IB<<1)); default: return; } @@ -434,20 +437,19 @@ const Image Tlg6Reader::read_raw_data(std::ifstream &ifs) const image.height = header.image_height; image.pixels = new uint32_t[image.width * image.height]; - uint32_t *pixel_buf = new uint32_t[4 * header.image_width * TLG6_H_BLOCK_SIZE]; + uint32_t *pixel_buf = new uint32_t[4 * header.image_width * h_block_size]; uint32_t *zeroline = new uint32_t[header.image_width]; uint32_t *prev_line = zeroline; for (uint32_t i = 0; i < header.image_width; i ++) zeroline[i] = 0; /* ! */ - - InitTLG6Table(); - uint32_t main_count = header.image_width / TLG6_W_BLOCK_SIZE; - int fraction = header.image_width - main_count * TLG6_W_BLOCK_SIZE; - for (uint32_t y = 0; y < header.image_height; y += TLG6_H_BLOCK_SIZE) + init_table(); + uint32_t main_count = header.image_width / w_block_size; + int fraction = header.image_width - main_count * w_block_size; + for (uint32_t y = 0; y < header.image_height; y += h_block_size) { - uint32_t ylim = y + TLG6_H_BLOCK_SIZE; + uint32_t ylim = y + h_block_size; if (ylim >= header.image_height) ylim = header.image_height; @@ -477,8 +479,8 @@ const Image Tlg6Reader::read_raw_data(std::ifstream &ifs) const } } - uint8_t *ft = filter_types.data.get() + (y / TLG6_H_BLOCK_SIZE) * header.x_block_count; - int skip_bytes = (ylim - y) * TLG6_W_BLOCK_SIZE; + uint8_t *ft = filter_types.data.get() + (y / h_block_size) * header.x_block_count; + int skip_bytes = (ylim - y) * w_block_size; for (uint32_t yy = y; yy < ylim; yy ++) { @@ -489,9 +491,9 @@ const Image Tlg6Reader::read_raw_data(std::ifstream &ifs) const if (main_count) { - int start = ((header.image_width < TLG6_W_BLOCK_SIZE) + int start = ((header.image_width < w_block_size) ? header.image_width - : TLG6_W_BLOCK_SIZE) * (yy - y); + : w_block_size) * (yy - y); decode_line( prev_line, @@ -512,8 +514,8 @@ const Image Tlg6Reader::read_raw_data(std::ifstream &ifs) const if (main_count != header.x_block_count) { int ww = fraction; - if (ww > TLG6_W_BLOCK_SIZE) - ww = TLG6_W_BLOCK_SIZE; + if (ww > w_block_size) + ww = w_block_size; int start = ww * (yy - y); decode_line( From 18192edbd4bd3128c472e203464faf615d93235a Mon Sep 17 00:00:00 2001 From: Marcin Kurczewski Date: Tue, 9 Dec 2014 21:39:08 +0100 Subject: [PATCH 09/17] Removed the rest of #defines --- src/Tlg6Reader.cc | 210 +++++++++++++++++++++++++++++----------------- 1 file changed, 135 insertions(+), 75 deletions(-) diff --git a/src/Tlg6Reader.cc b/src/Tlg6Reader.cc index 2d8eda2..b50ab75 100644 --- a/src/Tlg6Reader.cc +++ b/src/Tlg6Reader.cc @@ -1,5 +1,4 @@ -#include //! -#include //! +#include #include #include #include "LzssCompressionState.h" @@ -17,6 +16,104 @@ namespace uint8_t leading_zero_table[leading_zero_table_size]; uint8_t golomb_bit_length_table[golomb_n_count * 2 * 128][golomb_n_count]; + std::array transformers + { + [](uint8_t &r, uint8_t &g, uint8_t &b) + { + }, + + [](uint8_t &r, uint8_t &g, uint8_t &b) + { + r += g; + b += g; + }, + + [](uint8_t &r, uint8_t &g, uint8_t &b) + { + g += b; + r += g; + }, + + [](uint8_t &r, uint8_t &g, uint8_t &b) + { + g += r; + b += g; + }, + + [](uint8_t &r, uint8_t &g, uint8_t &b) + { + b += r; + g += b; + r += g; + }, + + [](uint8_t &r, uint8_t &g, uint8_t &b) + { + b += r; + g += b; + }, + + [](uint8_t &r, uint8_t &g, uint8_t &b) + { + b += g; + }, + + [](uint8_t &r, uint8_t &g, uint8_t &b) + { + g += b; + }, + + [](uint8_t &r, uint8_t &g, uint8_t &b) + { + r += g; + }, + + [](uint8_t &r, uint8_t &g, uint8_t &b) + { + r += b; + g += r; + b += g; + }, + + [](uint8_t &r, uint8_t &g, uint8_t &b) + { + b += r; + g += r; + }, + + [](uint8_t &r, uint8_t &g, uint8_t &b) + { + r += b; + g += b; + }, + + [](uint8_t &r, uint8_t &g, uint8_t &b) + { + r += b; + g += r; + }, + + [](uint8_t &r, uint8_t &g, uint8_t &b) + { + b += g; + r += b; + g += r; + }, + + [](uint8_t &r, uint8_t &g, uint8_t &b) + { + g += r; + b += g; + r += b; + }, + + [](uint8_t &r, uint8_t &g, uint8_t &b) + { + g += (b<<1); + r += (b<<1); + }, + }; + struct Tlg6Header { uint8_t channel_count; @@ -135,46 +232,6 @@ namespace + ((a ^ b) & 0x01010101), v); } - /* !! */ - - #define DO_CHROMA_DECODE_PROTO(B, G, R, A) \ - do \ - { \ - uint32_t u = *prev_line; \ - p = med(p, u, up, \ - (0xff0000 & ((B) << 16)) + (0xff00 & ((G) << 8)) + (0xff & (R)) + ((A) << 24)); \ - up = u; \ - *current_line = p; \ - current_line ++; \ - prev_line ++; \ - in += step; \ - } \ - while (-- w); - - #define DO_CHROMA_DECODE_PROTO2(B, G, R, A) \ - do \ - { \ - uint32_t u = *prev_line; \ - p = avg(p, u, up, \ - (0xff0000 & ((B) << 16)) + (0xff00 & ((G) << 8)) + (0xff & (R)) + ((A) << 24)); \ - up = u; \ - *current_line = p; \ - current_line ++; \ - prev_line ++; \ - in += step; \ - } \ - while (-- w); - - #define DO_CHROMA_DECODE(N, R, G, B) \ - case (N<<1): \ - DO_CHROMA_DECODE_PROTO(R, G, B, IA) \ - break; \ - case (N<<1)+1: \ - DO_CHROMA_DECODE_PROTO2(R, G, B, IA) \ - break; - - /* /!! */ - void init_table() { short golomb_compression_table[golomb_n_count][9] = @@ -369,43 +426,46 @@ namespace if (i & 1) in += odd_skip * ww; - switch (filtertypes[i]) + uint32_t (*filter)( + uint32_t const &, + uint32_t const &, + uint32_t const &, + uint32_t const &) + = filtertypes[i] & 1 ? &avg : &med; + + void (*transformer)(uint8_t &, uint8_t &, uint8_t &) + = transformers[filtertypes[i] >> 1]; + + do { - #define IA (uint8_t)((*in>>24)&0xff) - #define IR (uint8_t)((*in>>16)&0xff) - #define IG (uint8_t)((*in>>8)&0xff) - #define IB (uint8_t)((*in)&0xff) - DO_CHROMA_DECODE( 0, IB, IG, IR); - DO_CHROMA_DECODE( 1, IB+IG, IG, IR+IG); - DO_CHROMA_DECODE( 2, IB, IG+IB, IR+IB+IG); - DO_CHROMA_DECODE( 3, IB+IR+IG, IG+IR, IR); - DO_CHROMA_DECODE( 4, IB+IR, IG+IB+IR, IR+IB+IR+IG); - DO_CHROMA_DECODE( 5, IB+IR, IG+IB+IR, IR); - DO_CHROMA_DECODE( 6, IB+IG, IG, IR); - DO_CHROMA_DECODE( 7, IB, IG+IB, IR); - DO_CHROMA_DECODE( 8, IB, IG, IR+IG); - DO_CHROMA_DECODE( 9, IB+IG+IR+IB, IG+IR+IB, IR+IB); - DO_CHROMA_DECODE(10, IB+IR, IG+IR, IR); - DO_CHROMA_DECODE(11, IB, IG+IB, IR+IB); - DO_CHROMA_DECODE(12, IB, IG+IR+IB, IR+IB); - DO_CHROMA_DECODE(13, IB+IG, IG+IR+IB+IG, IR+IB+IG); - DO_CHROMA_DECODE(14, IB+IG+IR, IG+IR, IR+IB+IG+IR); - DO_CHROMA_DECODE(15, IB, IG+(IB<<1), IR+(IB<<1)); - - default: return; + uint8_t a = (*in >> 24) & 0xff; + uint8_t r = (*in >> 16) & 0xff; + uint8_t g = (*in >> 8) & 0xff; + uint8_t b = (*in) & 0xff; + + transformer(r, g, b); + + uint32_t u = *prev_line; + p = filter( + p, + u, + up, + (0xff0000 & (b << 16)) + + (0xff00 & (g << 8)) + + (0xff & r) + (a << 24)); + + up = u; + *current_line = p; + + current_line ++; + prev_line ++; + in += step; } + while (-- w); - if (step == 1) - in += skip_block_bytes - ww; - else - in += skip_block_bytes + 1; - - if (i&1) + in += skip_block_bytes + (step == 1 ? - ww : 1); + if (i & 1) in -= odd_skip * ww; - - #undef IR - #undef IG - #undef IB } } From 99e67b1839f3b30efe0fc0bc94bc39ac51381d3f Mon Sep 17 00:00:00 2001 From: Marcin Kurczewski Date: Tue, 9 Dec 2014 21:50:48 +0100 Subject: [PATCH 10/17] Improved formatting --- src/Tlg6Reader.cc | 21 +++++++-------------- 1 file changed, 7 insertions(+), 14 deletions(-) diff --git a/src/Tlg6Reader.cc b/src/Tlg6Reader.cc index b50ab75..777a82f 100644 --- a/src/Tlg6Reader.cc +++ b/src/Tlg6Reader.cc @@ -109,8 +109,8 @@ namespace [](uint8_t &r, uint8_t &g, uint8_t &b) { - g += (b<<1); - r += (b<<1); + g += (b << 1); + r += (b << 1); }, }; @@ -271,7 +271,7 @@ namespace } } - void TLG6DecodeGolombValues(uint8_t *pixel_buf, int pixel_count, uint8_t *bit_pool, int channel) + void decode_golomb_values(uint8_t *pixel_buf, int pixel_count, uint8_t *bit_pool, int channel) { int n = golomb_n_count - 1; int a = 0; @@ -319,8 +319,6 @@ namespace pixel_buf += 4; } while (-- count); - - zero ^= 1; } else { @@ -376,9 +374,9 @@ namespace } } while (-- count); - - zero ^= 1; } + + zero ^= 1; } } @@ -468,8 +466,6 @@ namespace in -= odd_skip * ww; } } - - /* ! */ } const std::string Tlg6Reader::get_magic() const @@ -503,7 +499,6 @@ const Image Tlg6Reader::read_raw_data(std::ifstream &ifs) const for (uint32_t i = 0; i < header.image_width; i ++) zeroline[i] = 0; - /* ! */ init_table(); uint32_t main_count = header.image_width / w_block_size; int fraction = header.image_width - main_count * w_block_size; @@ -531,7 +526,7 @@ const Image Tlg6Reader::read_raw_data(std::ifstream &ifs) const if (method == 0) { - TLG6DecodeGolombValues((uint8_t*)pixel_buf + c, pixel_count, bit_pool, c); + decode_golomb_values((uint8_t*)pixel_buf + c, pixel_count, bit_pool, c); } else { @@ -546,7 +541,7 @@ const Image Tlg6Reader::read_raw_data(std::ifstream &ifs) const { uint32_t *current_line = &image.pixels[yy * image.width]; - int dir = (yy&1)^1; + int dir = (yy & 1) ^ 1; int odd_skip = ((ylim - yy -1) - (yy - y)); if (main_count) @@ -598,7 +593,5 @@ const Image Tlg6Reader::read_raw_data(std::ifstream &ifs) const } } - /* ! */ - return image; } From 528d7c1ccefb60865c3da519158dbd984f3d9809 Mon Sep 17 00:00:00 2001 From: Marcin Kurczewski Date: Tue, 9 Dec 2014 22:36:13 +0100 Subject: [PATCH 11/17] Fixed problems with images not using alpha channel The problem persisted even after retracing back to initial KRKRZ port. Is it a bug in krkrtpc? --- src/Tlg6Reader.cc | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/Tlg6Reader.cc b/src/Tlg6Reader.cc index 777a82f..3d04e44 100644 --- a/src/Tlg6Reader.cc +++ b/src/Tlg6Reader.cc @@ -391,7 +391,8 @@ namespace uint32_t *in, uint32_t initialp, int odd_skip, - int dir) + int dir, + int channel_count) { uint32_t p, up; int step, i; @@ -452,6 +453,9 @@ namespace + (0xff00 & (g << 8)) + (0xff & r) + (a << 24)); + if (channel_count == 3) + p |= 0xff000000; + up = u; *current_line = p; @@ -563,7 +567,8 @@ const Image Tlg6Reader::read_raw_data(std::ifstream &ifs) const ? 0xff000000 : 0, odd_skip, - dir); + dir, + header.channel_count); } if (main_count != header.x_block_count) @@ -586,7 +591,8 @@ const Image Tlg6Reader::read_raw_data(std::ifstream &ifs) const ? 0xff000000 : 0, odd_skip, - dir); + dir, + header.channel_count); } prev_line = current_line; From 9ad5f8fe8441d430821daccf9c8a59b889440d22 Mon Sep 17 00:00:00 2001 From: Marcin Kurczewski Date: Wed, 10 Dec 2014 21:41:27 +0100 Subject: [PATCH 12/17] Cosmetic changes --- src/Tlg6Reader.cc | 28 ++++++++++++++++------------ 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/src/Tlg6Reader.cc b/src/Tlg6Reader.cc index 3d04e44..a808166 100644 --- a/src/Tlg6Reader.cc +++ b/src/Tlg6Reader.cc @@ -245,10 +245,13 @@ namespace for (int i = 0; i < leading_zero_table_size; i ++) { int cnt = 0; - int j; + int j = 1; - for (j = 1; j != leading_zero_table_size && !(i & j); - j <<= 1, cnt ++); + while (j != leading_zero_table_size && !(i & j)) + { + j <<= 1; + cnt ++; + } cnt ++; @@ -271,7 +274,7 @@ namespace } } - void decode_golomb_values(uint8_t *pixel_buf, int pixel_count, uint8_t *bit_pool, int channel) + void decode_golomb_values(uint8_t *pixel_buf, int pixel_count, uint8_t *bit_pool) { int n = golomb_n_count - 1; int a = 0; @@ -304,7 +307,8 @@ namespace bit_count --; count = 1 << bit_count; - count += ((*(uint32_t*)(bit_pool) >> (bit_pos)) & (count - 1)); + t = *(uint32_t*)(bit_pool); + count += ((t >> bit_pos) & (count - 1)); bit_pos += bit_count; bit_pool += bit_pos >> 3; @@ -386,7 +390,7 @@ namespace int width, int start_block, int block_limit, - uint8_t *filtertypes, + uint8_t *filter_types, int skip_block_bytes, uint32_t *in, uint32_t initialp, @@ -430,10 +434,10 @@ namespace uint32_t const &, uint32_t const &, uint32_t const &) - = filtertypes[i] & 1 ? &avg : &med; + = filter_types[i] & 1 ? &avg : &med; void (*transformer)(uint8_t &, uint8_t &, uint8_t &) - = transformers[filtertypes[i] >> 1]; + = transformers[filter_types[i] >> 1]; do { @@ -498,10 +502,10 @@ const Image Tlg6Reader::read_raw_data(std::ifstream &ifs) const image.pixels = new uint32_t[image.width * image.height]; uint32_t *pixel_buf = new uint32_t[4 * header.image_width * h_block_size]; - uint32_t *zeroline = new uint32_t[header.image_width]; - uint32_t *prev_line = zeroline; + uint32_t *zero_line = new uint32_t[header.image_width]; + uint32_t *prev_line = zero_line; for (uint32_t i = 0; i < header.image_width; i ++) - zeroline[i] = 0; + zero_line[i] = 0; init_table(); uint32_t main_count = header.image_width / w_block_size; @@ -530,7 +534,7 @@ const Image Tlg6Reader::read_raw_data(std::ifstream &ifs) const if (method == 0) { - decode_golomb_values((uint8_t*)pixel_buf + c, pixel_count, bit_pool, c); + decode_golomb_values((uint8_t*)pixel_buf + c, pixel_count, bit_pool); } else { From 43cf2b20b8e73a45f365c1a416dc8cc349ea21c4 Mon Sep 17 00:00:00 2001 From: Marcin Kurczewski Date: Wed, 10 Dec 2014 21:44:33 +0100 Subject: [PATCH 13/17] Removed Ruby version Its performance was already bad, but a benchmark on an incomplete port of the TLG6 reader proved that it was going to decrease even more. It simply made no sense to keep the Ruby implementation around if it's execution time was 30x slower (TLG5) and 250x slower (TLG6)... even after micro-optimizations. --- README.md | 6 +- src-ruby/README.md | 1 - src-ruby/lib/image.rb | 6 - src-ruby/lib/lzss_compression_state.rb | 13 --- src-ruby/lib/lzss_compressor.rb | 45 -------- src-ruby/lib/tlg0_reader.rb | 61 ---------- src-ruby/lib/tlg5_reader.rb | 147 ------------------------- src-ruby/lib/tlg6_reader.rb | 74 ------------- src-ruby/lib/tlg_converter.rb | 44 -------- src-ruby/lib/tlg_reader.rb | 10 -- src-ruby/tlg2png.rb | 63 ----------- 11 files changed, 2 insertions(+), 468 deletions(-) delete mode 100644 src-ruby/README.md delete mode 100644 src-ruby/lib/image.rb delete mode 100644 src-ruby/lib/lzss_compression_state.rb delete mode 100644 src-ruby/lib/lzss_compressor.rb delete mode 100644 src-ruby/lib/tlg0_reader.rb delete mode 100644 src-ruby/lib/tlg5_reader.rb delete mode 100644 src-ruby/lib/tlg6_reader.rb delete mode 100644 src-ruby/lib/tlg_converter.rb delete mode 100644 src-ruby/lib/tlg_reader.rb delete mode 100755 src-ruby/tlg2png.rb diff --git a/README.md b/README.md index 02644a6..d7fff00 100644 --- a/README.md +++ b/README.md @@ -3,8 +3,6 @@ Converts TLG images to PNG. TLG images are used by games based on Kirikiri engine, which includes titles such as Fate/Stay Night. -There are two implementations: C++ and Ruby. C++ is *much* faster, but since -nontechnical people might have hard time compiling it, I've kept Ruby version -around. +Requires libpng. To install it with apt-get, run: -C++ version requires libpng. Ruby version depends on RMagick gem. + apt-get install libpng-dev diff --git a/src-ruby/README.md b/src-ruby/README.md deleted file mode 100644 index 74d572a..0000000 --- a/src-ruby/README.md +++ /dev/null @@ -1 +0,0 @@ -Ruby port for easy tinkering/testing/you name it. diff --git a/src-ruby/lib/image.rb b/src-ruby/lib/image.rb deleted file mode 100644 index 4e32d22..0000000 --- a/src-ruby/lib/image.rb +++ /dev/null @@ -1,6 +0,0 @@ -# A class that represents an abstract image. -class Image - attr_accessor :width - attr_accessor :height - attr_accessor :pixels -end diff --git a/src-ruby/lib/lzss_compression_state.rb b/src-ruby/lib/lzss_compression_state.rb deleted file mode 100644 index c35eaf6..0000000 --- a/src-ruby/lib/lzss_compression_state.rb +++ /dev/null @@ -1,13 +0,0 @@ -# Holds TLG compression state. -class LzssCompressionState - # dictionary used by the modified LZW compression algorithm - attr_reader :text - - # offset within the dictionary - attr_accessor :offset - - def initialize - @text = Array(0..4095).fill(0) - @offset = 0 - end -end diff --git a/src-ruby/lib/lzss_compressor.rb b/src-ruby/lib/lzss_compressor.rb deleted file mode 100644 index e968168..0000000 --- a/src-ruby/lib/lzss_compressor.rb +++ /dev/null @@ -1,45 +0,0 @@ -# LZSS-based compressor used by TLG5 and TLG6. -class LzssCompressor - def self.decompress(compressor_state, input_data, input_size) - output_data = [] - flags = 0 - i = 0 - while i < input_size - flags >>= 1 - if (flags & 0x100) != 0x100 - flags = input_data[i] | 0xff00 - i += 1 - end - - if (flags & 1) == 1 - x0, x1 = input_data[i], input_data[i + 1] - i += 2 - position = x0 | ((x1 & 0xf) << 8) - length = 3 + ((x1 & 0xf0) >> 4) - if length == 18 - length += input_data[i] - i += 1 - end - j = 0 - while j < length - c = compressor_state.text[position] - output_data << c - compressor_state.text[compressor_state.offset] = c - compressor_state.offset += 1 - compressor_state.offset &= 0xfff - position += 1 - position &= 0xfff - j += 1 - end - else - c = input_data[i] - i += 1 - output_data << c - compressor_state.text[compressor_state.offset] = c - compressor_state.offset += 1 - compressor_state.offset &= 0xfff - end - end - output_data - end -end diff --git a/src-ruby/lib/tlg0_reader.rb b/src-ruby/lib/tlg0_reader.rb deleted file mode 100644 index 578078a..0000000 --- a/src-ruby/lib/tlg0_reader.rb +++ /dev/null @@ -1,61 +0,0 @@ -require_relative 'tlg_reader' -require_relative 'tlg5_reader' -require_relative 'tlg6_reader' -require 'rubygems' -require 'RMagick' - -# A TLG reader that handles TLG "version" 0. -# -# TLG0 is just a wrapper for TLG5/TLG6 that puts additional data at the end of -# the file. Currently, the only data it supports are tags. -class Tlg0Reader < TlgReader - MAGIC = "\x54\x4c\x47\x30\x2e\x30\x00\x73\x64\x73\x1a" - - def read_stream(file) - assert_magic(file) - _raw_data_size = file.read(4).unpack(' @header.image_height - use_alpha = @header.channel_count == 4 - - (block_y..(max_y - 1)).each do |y| - prev_red = 0 - prev_green = 0 - prev_blue = 0 - prev_alpha = 0 - - block_y_shift = (y - block_y) * @header.image_width - prev_y_shift = (y - 1) * @header.image_width - - (0..@header.image_width - 1).each do |x| - red = channel_data[2][block_y_shift + x] - green = channel_data[1][block_y_shift + x] - blue = channel_data[0][block_y_shift + x] - alpha = use_alpha ? channel_data[3][block_y_shift + x] : 0 - - red += green - blue += green - - prev_red += red - prev_green += green - prev_blue += blue - prev_alpha += alpha - - output_red = prev_red - output_green = prev_green - output_blue = prev_blue - output_alpha = prev_alpha - - if y > 0 - index = (prev_y_shift + x) * 4 - output_red += output_pixels[index + 0] - output_green += output_pixels[index + 1] - output_blue += output_pixels[index + 2] - output_alpha += output_pixels[index + 3] - end - - output_alpha = 255 unless use_alpha - - output_pixels << output_red - output_pixels << output_green - output_pixels << output_blue - output_pixels << output_alpha - end - end - end - - def read_channel_data(file) - channel_data = {} - (0..@header.channel_count - 1).each do |channel| - block_info = Tlg5BlockInfo.new(file) - block_info.decompress(@compression_state) unless block_info.mark - channel_data[channel] = block_info.block_data - end - channel_data - end - - # A block information. - class Tlg5BlockInfo - # Is decompressed? - attr_reader :mark - - # The size of :block_data. - attr_reader :block_size - - # It holds enough data to read up to :block_size pixel rows for one channel. - attr_accessor :block_data - - def initialize(file) - @mark = file.read(1).unpack('C')[0] > 0 - @block_size = file.read(4).unpack(' e - puts e.message - puts - puts opt_parser - exit(1) - end - - if ARGV.length != 2 - puts opt_parser - exit(1) - end - - @options.input_path = ARGV.first - @options.output_path = ARGV.last - end - - def escape(string) - string - .sub(/^\//, '') - .gsub(/([^ a-zA-Z0-9_.-]+)/n, '_') - end -end - -CLI.new.run From c53ab8a427897390cfde55e79c4b90794ec1183f Mon Sep 17 00:00:00 2001 From: Marcin Kurczewski Date: Wed, 10 Dec 2014 21:48:05 +0100 Subject: [PATCH 14/17] Removed debugging symbols --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index cddaf84..f410cd1 100644 --- a/Makefile +++ b/Makefile @@ -5,7 +5,7 @@ EXECUTABLE := tlg2png SOURCES := $(wildcard $(SRCDIR)*.cc) OBJECTS := $(SOURCES:$(SRCDIR)%.cc=$(OBJDIR)%.o) -CXXFLAGS := -Wall -pedantic -std=c++11 -g +CXXFLAGS := -Wall -pedantic -std=c++11 LFLAGS := -lpng all: $(EXECUTABLE) From a74ce1a823bea545691846d9fc436f7944dd8e2f Mon Sep 17 00:00:00 2001 From: Marcin Kurczewski Date: Wed, 10 Dec 2014 21:49:19 +0100 Subject: [PATCH 15/17] Added link to releases --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index d7fff00..5bfdbbe 100644 --- a/README.md +++ b/README.md @@ -6,3 +6,6 @@ such as Fate/Stay Night. Requires libpng. To install it with apt-get, run: apt-get install libpng-dev + +To download precompiled Windows binaries, head over to +[releases](https://github.com/vn-tools/tlg2png/releases/). From 38112c26cf349d896ec418b77a2fe9904c97b770 Mon Sep 17 00:00:00 2001 From: Marcin Kurczewski Date: Wed, 10 Dec 2014 22:48:59 +0100 Subject: [PATCH 16/17] Added mingw support --- Makefile | 3 +++ src/AbstractTlgReader.cc | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/Makefile b/Makefile index f410cd1..fe0a217 100644 --- a/Makefile +++ b/Makefile @@ -7,6 +7,9 @@ SOURCES := $(wildcard $(SRCDIR)*.cc) OBJECTS := $(SOURCES:$(SRCDIR)%.cc=$(OBJDIR)%.o) CXXFLAGS := -Wall -pedantic -std=c++11 LFLAGS := -lpng +#mingw +#CXXFLAGS := -Wall -pedantic -std=c++11 -static-libgcc -static-libstdc++ +#LFLAGS := -llibpng all: $(EXECUTABLE) diff --git a/src/AbstractTlgReader.cc b/src/AbstractTlgReader.cc index 2fb88bb..eadae9d 100644 --- a/src/AbstractTlgReader.cc +++ b/src/AbstractTlgReader.cc @@ -19,7 +19,7 @@ std::unique_ptr new Tlg6Reader(), }; - off_t pos = ifs.tellg(); + auto pos = ifs.tellg(); for (auto reader : readers) { try From 76d383270cfe35509e0e6722e0c4d03790a0e29f Mon Sep 17 00:00:00 2001 From: Marcin Kurczewski Date: Wed, 10 Dec 2014 22:49:16 +0100 Subject: [PATCH 17/17] Fixed typo --- src/main.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main.cc b/src/main.cc index b4f3cf6..d04b3c8 100644 --- a/src/main.cc +++ b/src/main.cc @@ -5,7 +5,7 @@ void show_usage(std::string path_to_self) { - std::cerr << "Usage " << path_to_self << " INPUT OUTPUT" << std::endl; + std::cerr << "Usage: " << path_to_self << " INPUT OUTPUT" << std::endl; } int main(int argc, char **argv)