Continue png writing.
This commit is contained in:
parent
5400a232dd
commit
8f97e9b7a1
29 changed files with 714 additions and 302 deletions
|
@ -1,5 +1,9 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include "AbstractChecksumCalculator.h"
|
||||||
|
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
class BitStream;
|
class BitStream;
|
||||||
|
|
||||||
class AbstractEncoder
|
class AbstractEncoder
|
||||||
|
@ -14,10 +18,17 @@ public:
|
||||||
|
|
||||||
virtual ~AbstractEncoder() = default;
|
virtual ~AbstractEncoder() = default;
|
||||||
|
|
||||||
|
void addChecksumCalculator(AbstractChecksumCalculator* calculator)
|
||||||
|
{
|
||||||
|
mChecksumCalculators.push_back(calculator);
|
||||||
|
}
|
||||||
|
|
||||||
virtual bool encode() = 0;
|
virtual bool encode() = 0;
|
||||||
virtual bool decode() = 0;
|
virtual bool decode() = 0;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
|
|
||||||
|
std::vector<AbstractChecksumCalculator*> mChecksumCalculators;
|
||||||
BitStream* mInputStream{nullptr};
|
BitStream* mInputStream{nullptr};
|
||||||
BitStream* mOutputStream{nullptr};
|
BitStream* mOutputStream{nullptr};
|
||||||
};
|
};
|
||||||
|
|
29
src/compression/Adler32Checksum.h
Normal file
29
src/compression/Adler32Checksum.h
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "AbstractChecksumCalculator.h"
|
||||||
|
|
||||||
|
class Adler32Checksum : public AbstractChecksumCalculator
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
void addValue(unsigned char val) override
|
||||||
|
{
|
||||||
|
mSum1 = (mSum1 + val) % MOD_ADLER32;
|
||||||
|
mSum2 = (mSum2 + mSum1) % MOD_ADLER32;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t getChecksum() const override
|
||||||
|
{
|
||||||
|
return (mSum2 << 16) | mSum1;
|
||||||
|
}
|
||||||
|
|
||||||
|
void reset() override
|
||||||
|
{
|
||||||
|
mSum1 = 1;
|
||||||
|
mSum2 = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
static constexpr unsigned MOD_ADLER32{65536};
|
||||||
|
uint32_t mSum1{1};
|
||||||
|
uint32_t mSum2{0};
|
||||||
|
};
|
|
@ -3,10 +3,9 @@ list(APPEND compression_LIB_INCLUDES
|
||||||
StreamCompressor.cpp
|
StreamCompressor.cpp
|
||||||
HuffmanEncoder.cpp
|
HuffmanEncoder.cpp
|
||||||
RunLengthEncoder.cpp
|
RunLengthEncoder.cpp
|
||||||
ZlibData.cpp
|
|
||||||
ZlibEncoder.cpp
|
ZlibEncoder.cpp
|
||||||
DeflateEncoder.cpp
|
deflate/DeflateEncoder.cpp
|
||||||
DeflateBlock.cpp
|
deflate/DeflateBlock.cpp
|
||||||
Lz77Encoder.cpp
|
Lz77Encoder.cpp
|
||||||
CyclicRedundancyChecker.cpp
|
CyclicRedundancyChecker.cpp
|
||||||
)
|
)
|
||||||
|
@ -14,7 +13,8 @@ list(APPEND compression_LIB_INCLUDES
|
||||||
add_library(compression SHARED ${compression_LIB_INCLUDES})
|
add_library(compression SHARED ${compression_LIB_INCLUDES})
|
||||||
|
|
||||||
target_include_directories(compression PUBLIC
|
target_include_directories(compression PUBLIC
|
||||||
"${CMAKE_CURRENT_SOURCE_DIR}"
|
${CMAKE_CURRENT_SOURCE_DIR}
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/deflate
|
||||||
)
|
)
|
||||||
|
|
||||||
target_link_libraries(compression PUBLIC core)
|
target_link_libraries(compression PUBLIC core)
|
||||||
|
|
|
@ -1,38 +0,0 @@
|
||||||
#include "DeflateEncoder.h"
|
|
||||||
|
|
||||||
#include "BitStream.h"
|
|
||||||
#include "DeflateBlock.h"
|
|
||||||
|
|
||||||
#include <iostream>
|
|
||||||
|
|
||||||
DeflateEncoder::DeflateEncoder(BitStream* inputStream, BitStream* outputStream)
|
|
||||||
: AbstractEncoder(inputStream, outputStream)
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
DeflateEncoder::~DeflateEncoder()
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
bool DeflateEncoder::encode()
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool DeflateEncoder::decode()
|
|
||||||
{
|
|
||||||
auto working_block = std::make_unique<DeflateBlock>(mInputStream, mOutputStream);
|
|
||||||
working_block->readHeader();
|
|
||||||
|
|
||||||
DeflateBlock* raw_block = working_block.get();
|
|
||||||
|
|
||||||
while(!raw_block->isFinalBlock())
|
|
||||||
{
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,107 +0,0 @@
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include "ByteUtils.h"
|
|
||||||
#include "BitStream.h"
|
|
||||||
|
|
||||||
#include <vector>
|
|
||||||
#include <iostream>
|
|
||||||
|
|
||||||
class ZlibData
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
void setByte(unsigned idx, unsigned char data)
|
|
||||||
{
|
|
||||||
mBitStream.setByte(idx, data);
|
|
||||||
}
|
|
||||||
|
|
||||||
void setDataSize(std::size_t size)
|
|
||||||
{
|
|
||||||
mBitStream.setBufferSize(size);
|
|
||||||
}
|
|
||||||
|
|
||||||
void setCompressionMethod(unsigned char method)
|
|
||||||
{
|
|
||||||
std::cout << "Got compression input " << static_cast<int>(method) << std::endl;
|
|
||||||
mCmf = method;
|
|
||||||
mCompressionMethod = ByteUtils::getLowerNBits(method, 4);
|
|
||||||
mCompressionInfo = ByteUtils::getHigherNBits(method, 4);
|
|
||||||
|
|
||||||
std::cout << "Got compression method " << static_cast<int>(mCompressionMethod) << " and info " << static_cast<int>(mCompressionInfo) << std::endl;
|
|
||||||
}
|
|
||||||
|
|
||||||
void setExtraFlags(unsigned char extraFlags)
|
|
||||||
{
|
|
||||||
std::cout << "Got flags " << static_cast<int>(extraFlags) << std::endl;
|
|
||||||
|
|
||||||
mFlg = extraFlags;
|
|
||||||
mFlagCheck = ByteUtils::getLowerNBits(extraFlags, 5);
|
|
||||||
mFlagDict = ByteUtils::getBitN(extraFlags, 5);
|
|
||||||
mFlagLevel = ByteUtils::getHigherNBits(extraFlags, 2);
|
|
||||||
|
|
||||||
std::cout << "Got flag check " << static_cast<int>(mFlagCheck) << " and dict " << static_cast<int>(mFlagDict) << " and level " << static_cast<int>(mFlagLevel) << std::endl;
|
|
||||||
}
|
|
||||||
|
|
||||||
void processData()
|
|
||||||
{
|
|
||||||
unsigned char NO_COMPRESSION = 0x00;
|
|
||||||
unsigned char FIXED_HUFFMAN = 0x01;
|
|
||||||
unsigned char DYNAMIC_HUFFMAN = 0x02;
|
|
||||||
unsigned char ERROR = 0x03;
|
|
||||||
|
|
||||||
bool in_final_block = false;
|
|
||||||
while(mBitStream.loadNextByte())
|
|
||||||
{
|
|
||||||
auto working_byte = mBitStream.getCurrentByte();
|
|
||||||
std::cout << "Into process data, byte is: " << static_cast<unsigned>(working_byte) << std::endl;
|
|
||||||
|
|
||||||
unsigned char final_block{0};
|
|
||||||
mBitStream.getNextNBits(1, final_block);
|
|
||||||
if (final_block)
|
|
||||||
{
|
|
||||||
std::cout << "Got final block" << std::endl;
|
|
||||||
in_final_block = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
unsigned char compress_type{0};
|
|
||||||
mBitStream.getNextNBits(2, compress_type);
|
|
||||||
std::cout << "Compress type byte is: " << static_cast<unsigned>(compress_type) << std::endl;
|
|
||||||
if (compress_type == NO_COMPRESSION)
|
|
||||||
{
|
|
||||||
std::cout << "Got NO_COMPRESSION" << std::endl;
|
|
||||||
}
|
|
||||||
else if (compress_type == FIXED_HUFFMAN)
|
|
||||||
{
|
|
||||||
std::cout << "Got FIXED_HUFFMAN" << std::endl;
|
|
||||||
}
|
|
||||||
else if (compress_type == DYNAMIC_HUFFMAN)
|
|
||||||
{
|
|
||||||
std::cout << "Got DYNAMIC_HUFFMAN" << std::endl;
|
|
||||||
|
|
||||||
unsigned char h_list{0};
|
|
||||||
mBitStream.getNextNBits(5, h_list);
|
|
||||||
mHlist = h_list + 257;
|
|
||||||
std::cout << "Got HLIST " << mHlist << std::endl;
|
|
||||||
|
|
||||||
}
|
|
||||||
else if (compress_type == ERROR)
|
|
||||||
{
|
|
||||||
std::cout << "Got ERROR" << std::endl;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
BitStream mBitStream;
|
|
||||||
|
|
||||||
unsigned mHlist{0};
|
|
||||||
|
|
||||||
unsigned char mCmf{0};
|
|
||||||
unsigned char mFlg{0};
|
|
||||||
unsigned char mCompressionMethod{0};
|
|
||||||
unsigned char mCompressionInfo{0};
|
|
||||||
unsigned char mFlagCheck{0};
|
|
||||||
unsigned char mFlagDict{0};
|
|
||||||
unsigned char mFlagLevel{0};
|
|
||||||
unsigned char mCheckValue{0};
|
|
||||||
};
|
|
|
@ -3,13 +3,18 @@
|
||||||
#include "ByteUtils.h"
|
#include "ByteUtils.h"
|
||||||
#include "DeflateEncoder.h"
|
#include "DeflateEncoder.h"
|
||||||
#include "FileLogger.h"
|
#include "FileLogger.h"
|
||||||
|
#include "BitStream.h"
|
||||||
|
|
||||||
|
#include "Adler32Checksum.h"
|
||||||
|
|
||||||
|
#include <math.h>
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
|
#include <sstream>
|
||||||
|
|
||||||
ZlibEncoder::ZlibEncoder(BitStream* inputStream, BitStream* outputStream)
|
ZlibEncoder::ZlibEncoder(BitStream* inputStream, BitStream* outputStream)
|
||||||
: AbstractEncoder(inputStream, outputStream)
|
: AbstractEncoder(inputStream, outputStream)
|
||||||
{
|
{
|
||||||
|
mChecksumCalculator = std::make_unique<Adler32Checksum>();
|
||||||
}
|
}
|
||||||
|
|
||||||
ZlibEncoder::~ZlibEncoder()
|
ZlibEncoder::~ZlibEncoder()
|
||||||
|
@ -17,58 +22,132 @@ ZlibEncoder::~ZlibEncoder()
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void ZlibEncoder::setCompressionMethod(unsigned char method)
|
void ZlibEncoder::setWindowSize(unsigned size)
|
||||||
{
|
{
|
||||||
std::cout << "Got compression input " << static_cast<int>(method) << std::endl;
|
mWindowSize = size;
|
||||||
mCmf = method;
|
|
||||||
mCompressionMethod = ByteUtils::getLowerNBits(method, 4);
|
|
||||||
mCompressionInfo = ByteUtils::getHigherNBits(method, 4);
|
|
||||||
|
|
||||||
std::cout << "Got compression method " << static_cast<int>(mCompressionMethod) << " and info " << static_cast<int>(mCompressionInfo) << std::endl;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void ZlibEncoder::setExtraFlags(unsigned char extraFlags)
|
std::string ZlibEncoder::toString(CompressionLevel level) const
|
||||||
|
{
|
||||||
|
switch(level)
|
||||||
|
{
|
||||||
|
case CompressionLevel::FASTEST:
|
||||||
|
return "FASTEST";
|
||||||
|
case CompressionLevel::FAST:
|
||||||
|
return "FAST";
|
||||||
|
case CompressionLevel::DEFAULT:
|
||||||
|
return "DEFAULT";
|
||||||
|
case CompressionLevel::MAX_COMPRESSION:
|
||||||
|
return "MAX_COMPRESSION";
|
||||||
|
default:
|
||||||
|
return "UNKNOWN";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string ZlibEncoder::toString(CompressionMethod method) const
|
||||||
|
{
|
||||||
|
return method == CompressionMethod::DEFLATE ? "DEFLATE" : "UNKNOWN";
|
||||||
|
}
|
||||||
|
|
||||||
|
void ZlibEncoder::parseCompressionMethod(unsigned char method)
|
||||||
|
{
|
||||||
|
std::cout << "Got compression input " << static_cast<int>(method) << std::endl;
|
||||||
|
mCompressionMethod = static_cast<CompressionMethod>(ByteUtils::getLowerNBits(method, 4));
|
||||||
|
auto compression_info = ByteUtils::getHigherNBits(method, 4);
|
||||||
|
|
||||||
|
if (mCompressionMethod == CompressionMethod::DEFLATE)
|
||||||
|
{
|
||||||
|
mWindowSize = pow(2, compression_info + 8);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ZlibEncoder::parseExtraFlags(unsigned char extraFlags)
|
||||||
{
|
{
|
||||||
std::cout << "Got flags " << static_cast<int>(extraFlags) << std::endl;
|
std::cout << "Got flags " << static_cast<int>(extraFlags) << std::endl;
|
||||||
|
|
||||||
mFlg = extraFlags;
|
|
||||||
mFlagCheck = ByteUtils::getLowerNBits(extraFlags, 5);
|
mFlagCheck = ByteUtils::getLowerNBits(extraFlags, 5);
|
||||||
mFlagDict = ByteUtils::getBitN(extraFlags, 5);
|
mUseDictionary = bool(ByteUtils::getBitN(extraFlags, 5));
|
||||||
mFlagLevel = ByteUtils::getHigherNBits(extraFlags, 2);
|
mFlagLevel = static_cast<CompressionLevel>(ByteUtils::getHigherNBits(extraFlags, 2));
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string ZlibEncoder::getData() const
|
||||||
|
{
|
||||||
|
std::stringstream sstream;
|
||||||
|
sstream << "ZlibEncoder data \n";
|
||||||
|
sstream << "Compression method: " << toString(mCompressionMethod) << '\n';
|
||||||
|
sstream << "Window size: " << mWindowSize << '\n';
|
||||||
|
sstream << "Flag check: " << mFlagCheck << '\n';
|
||||||
|
sstream << "Use dictionary: " << mUseDictionary << '\n';
|
||||||
|
sstream << "Flag level: " << toString(mFlagLevel) << '\n';
|
||||||
|
return sstream.str();
|
||||||
|
|
||||||
std::cout << "Got flag check " << static_cast<int>(mFlagCheck) << " and dict " << static_cast<int>(mFlagDict) << " and level " << static_cast<int>(mFlagLevel) << std::endl;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ZlibEncoder::encode()
|
bool ZlibEncoder::encode()
|
||||||
{
|
{
|
||||||
|
DeflateEncoder* deflate_encoder{nullptr};
|
||||||
if (!mWorkingEncoder)
|
if (!mWorkingEncoder)
|
||||||
{
|
{
|
||||||
if (mCompressionMethod == 8)
|
if (mCompressionMethod == CompressionMethod::DEFLATE)
|
||||||
{
|
{
|
||||||
mWorkingEncoder = std::make_unique<DeflateEncoder>(mInputStream, mOutputStream);
|
auto uq_deflate_encoder = std::make_unique<DeflateEncoder>(mInputStream, mOutputStream);
|
||||||
|
deflate_encoder = uq_deflate_encoder.get();
|
||||||
|
mWorkingEncoder = std::move(uq_deflate_encoder);
|
||||||
|
mWorkingEncoder->addChecksumCalculator(mChecksumCalculator.get());
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
MLOG_ERROR("Zib requested decoder not recognized: " << mCompressionMethod << " aborting encode");
|
MLOG_ERROR("Zib requested decoder not recognized: " << static_cast<int>(mCompressionMethod) << " aborting encode");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return mWorkingEncoder->encode();
|
deflate_encoder->setCompressionMethod(mDeflateCompressionMethod);
|
||||||
|
|
||||||
|
auto compression_info = static_cast<unsigned char>(log2(mWindowSize) - 8);
|
||||||
|
const unsigned char compression_byte = (compression_info << 4) | static_cast<unsigned char>(mCompressionMethod);
|
||||||
|
|
||||||
|
std::cout << "ZlibEncoder Writing compression byte " << static_cast<int>(compression_byte) << " with info " << static_cast<int>(compression_info) << std::endl;
|
||||||
|
mOutputStream->writeByte(compression_byte);
|
||||||
|
|
||||||
|
unsigned char flag_byte{0};
|
||||||
|
flag_byte |= (static_cast<unsigned char>(mUseDictionary) << 5);
|
||||||
|
flag_byte |= (static_cast<unsigned char>(mFlagLevel) << 6);
|
||||||
|
|
||||||
|
const auto mod = (unsigned(compression_byte)*256 + flag_byte) % 31;
|
||||||
|
flag_byte += (31 - mod);
|
||||||
|
|
||||||
|
std::cout << "ZlibEncoder Writing Flag byte " << static_cast<int>(flag_byte) << std::endl;
|
||||||
|
mOutputStream->writeByte(flag_byte);
|
||||||
|
|
||||||
|
if(!mWorkingEncoder->encode())
|
||||||
|
{
|
||||||
|
MLOG_ERROR("Sub-Encoder failed - aborting zlib encode");
|
||||||
|
//return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto checksum = mChecksumCalculator->getChecksum();
|
||||||
|
std::cout << "ZlibEncoder Writing Checksum " << checksum << std::endl;
|
||||||
|
mOutputStream->write(checksum);
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ZlibEncoder::decode()
|
bool ZlibEncoder::decode()
|
||||||
{
|
{
|
||||||
|
parseCompressionMethod(*mInputStream->readNextByte());
|
||||||
|
parseExtraFlags(*mInputStream->readNextByte());
|
||||||
|
|
||||||
if (!mWorkingEncoder)
|
if (!mWorkingEncoder)
|
||||||
{
|
{
|
||||||
if (mCompressionMethod == 8)
|
if (mCompressionMethod == CompressionMethod::DEFLATE)
|
||||||
{
|
{
|
||||||
mWorkingEncoder = std::make_unique<DeflateEncoder>(mInputStream, mOutputStream);
|
mWorkingEncoder = std::make_unique<DeflateEncoder>(mInputStream, mOutputStream);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
MLOG_ERROR("Zib requested decoder not recognized: " << mCompressionMethod << " aborting decode");
|
MLOG_ERROR("Zib requested decoder not recognized: " << static_cast<int>(mCompressionMethod) << " aborting decode");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return mWorkingEncoder->decode();
|
return mWorkingEncoder->decode();
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,33 +2,54 @@
|
||||||
|
|
||||||
#include "AbstractEncoder.h"
|
#include "AbstractEncoder.h"
|
||||||
|
|
||||||
|
#include "DeflateElements.h"
|
||||||
|
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
|
class AbstractChecksumCalculator;
|
||||||
|
|
||||||
class ZlibEncoder : public AbstractEncoder
|
class ZlibEncoder : public AbstractEncoder
|
||||||
{
|
{
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
|
||||||
|
enum class CompressionMethod : unsigned char
|
||||||
|
{
|
||||||
|
DEFLATE = 8,
|
||||||
|
};
|
||||||
|
|
||||||
|
enum class CompressionLevel : unsigned char
|
||||||
|
{
|
||||||
|
FASTEST,
|
||||||
|
FAST,
|
||||||
|
DEFAULT,
|
||||||
|
MAX_COMPRESSION
|
||||||
|
};
|
||||||
|
|
||||||
ZlibEncoder(BitStream* inputStream, BitStream* outputStream);
|
ZlibEncoder(BitStream* inputStream, BitStream* outputStream);
|
||||||
~ZlibEncoder();
|
~ZlibEncoder();
|
||||||
|
|
||||||
void setCompressionMethod(unsigned char method);
|
void setWindowSize(unsigned size);
|
||||||
|
|
||||||
void setExtraFlags(unsigned char extraFlags);
|
|
||||||
|
|
||||||
bool encode() override;
|
bool encode() override;
|
||||||
|
|
||||||
bool decode() override;
|
bool decode() override;
|
||||||
|
|
||||||
private:
|
std::string getData() const;
|
||||||
unsigned char mCmf{0};
|
std::string toString(CompressionLevel level) const;
|
||||||
unsigned char mFlg{0};
|
std::string toString(CompressionMethod method) const;
|
||||||
unsigned char mCompressionMethod{8};
|
|
||||||
unsigned char mCompressionInfo{0};
|
|
||||||
unsigned char mFlagCheck{0};
|
|
||||||
unsigned char mFlagDict{0};
|
|
||||||
unsigned char mFlagLevel{0};
|
|
||||||
unsigned char mCheckValue{0};
|
|
||||||
|
|
||||||
|
private:
|
||||||
|
void parseCompressionMethod(unsigned char method);
|
||||||
|
void parseExtraFlags(unsigned char extraFlags);
|
||||||
|
|
||||||
|
CompressionMethod mCompressionMethod{CompressionMethod::DEFLATE};
|
||||||
|
Deflate::CompressionMethod mDeflateCompressionMethod{Deflate::CompressionMethod::NONE};
|
||||||
|
unsigned mWindowSize{32768}; // Window size, n in 2^(n+8) bytes
|
||||||
|
|
||||||
|
unsigned char mFlagCheck{0};
|
||||||
|
bool mUseDictionary{false};
|
||||||
|
CompressionLevel mFlagLevel{CompressionLevel::DEFAULT};
|
||||||
|
|
||||||
|
std::unique_ptr<AbstractChecksumCalculator> mChecksumCalculator;
|
||||||
std::unique_ptr<AbstractEncoder> mWorkingEncoder;
|
std::unique_ptr<AbstractEncoder> mWorkingEncoder;
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,9 +1,11 @@
|
||||||
#include "DeflateBlock.h"
|
#include "DeflateBlock.h"
|
||||||
|
|
||||||
#include "ByteUtils.h"
|
#include "ByteUtils.h"
|
||||||
|
#include "AbstractChecksumCalculator.h"
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
|
#include <sstream>
|
||||||
|
|
||||||
DeflateBlock::DeflateBlock(BitStream* inputStream, BitStream* outputStream)
|
DeflateBlock::DeflateBlock(BitStream* inputStream, BitStream* outputStream)
|
||||||
: mInputStream(inputStream),
|
: mInputStream(inputStream),
|
||||||
|
@ -88,7 +90,7 @@ void DeflateBlock::setDistanceTableLength(unsigned length)
|
||||||
|
|
||||||
void DeflateBlock::setIsFinalBlock(bool isFinal)
|
void DeflateBlock::setIsFinalBlock(bool isFinal)
|
||||||
{
|
{
|
||||||
|
mInFinalBlock = isFinal;
|
||||||
}
|
}
|
||||||
|
|
||||||
void DeflateBlock::flushToStream()
|
void DeflateBlock::flushToStream()
|
||||||
|
@ -224,6 +226,22 @@ void DeflateBlock::readDynamicHuffmanTable()
|
||||||
readLiteralCodeLengths();
|
readLiteralCodeLengths();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::string DeflateBlock::getMetaData() const
|
||||||
|
{
|
||||||
|
std::stringstream sstr;
|
||||||
|
sstr << "DeflateBlock Metadata \n";
|
||||||
|
|
||||||
|
sstr << "Final block: " << mInFinalBlock << '\n';
|
||||||
|
sstr << "Compression method: " << Deflate::toString(mCompressionMethod) << '\n';
|
||||||
|
|
||||||
|
return sstr.str();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool DeflateBlock::isFinalBlock() const
|
||||||
|
{
|
||||||
|
return mInFinalBlock;
|
||||||
|
}
|
||||||
|
|
||||||
void DeflateBlock::readHeader()
|
void DeflateBlock::readHeader()
|
||||||
{
|
{
|
||||||
auto working_byte = mInputStream->getCurrentByte();
|
auto working_byte = mInputStream->getCurrentByte();
|
||||||
|
@ -234,29 +252,33 @@ void DeflateBlock::readHeader()
|
||||||
mInputStream->readNextNBits(1, final_block);
|
mInputStream->readNextNBits(1, final_block);
|
||||||
mInFinalBlock = bool(final_block);
|
mInFinalBlock = bool(final_block);
|
||||||
|
|
||||||
if (mInFinalBlock)
|
unsigned char compression_type{0};
|
||||||
{
|
mInputStream->readNextNBits(2, compression_type);
|
||||||
std::cout << "Got final block" << std::endl;
|
mCompressionMethod = static_cast<Deflate::CompressionMethod>(compression_type);
|
||||||
}
|
}
|
||||||
|
|
||||||
mInputStream->readNextNBits(2, mCompressionType);
|
void DeflateBlock::write(uint16_t datalength)
|
||||||
std::cout << "Compress type byte is: " << static_cast<unsigned>(mCompressionType) << std::endl;
|
|
||||||
if (mCompressionType == NO_COMPRESSION)
|
|
||||||
{
|
{
|
||||||
std::cout << "Got NO_COMPRESSION" << std::endl;
|
unsigned char working_block{0};
|
||||||
}
|
working_block |= static_cast<unsigned char>(mInFinalBlock);
|
||||||
else if (mCompressionType == FIXED_HUFFMAN)
|
working_block |= static_cast<unsigned char>(mCompressionMethod) << 1;
|
||||||
{
|
|
||||||
std::cout << "Got FIXED_HUFFMAN" << std::endl;
|
|
||||||
}
|
|
||||||
else if (mCompressionType == DYNAMIC_HUFFMAN)
|
|
||||||
{
|
|
||||||
std::cout << "Got DYNAMIC_HUFFMAN" << std::endl;
|
|
||||||
readDynamicHuffmanTable();
|
|
||||||
}
|
|
||||||
else if (mCompressionType == ERROR)
|
|
||||||
{
|
|
||||||
std::cout << "Got ERROR" << std::endl;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
if (mCompressionMethod == Deflate::CompressionMethod::NONE)
|
||||||
|
{
|
||||||
|
std::cout << "Writing compression block header " << static_cast<int>(working_block) << std::endl;
|
||||||
|
mOutputStream->writeByte(working_block);
|
||||||
|
|
||||||
|
std::cout << "Writing data length " << datalength << " " << ByteUtils::toString(datalength) << std::endl;
|
||||||
|
mOutputStream->writeWord(datalength);
|
||||||
|
|
||||||
|
std::cout << "Writing iverse data length " << ~datalength << " " << ByteUtils::toString(~datalength) << std::endl;
|
||||||
|
mOutputStream->writeWord(static_cast<uint16_t>(~datalength));
|
||||||
|
|
||||||
|
for(unsigned idx=0; idx<datalength;idx++)
|
||||||
|
{
|
||||||
|
auto byte = *mInputStream->readNextByte();
|
||||||
|
std::cout << "Writing next byte " << static_cast<int>(byte) << std::endl;
|
||||||
|
mOutputStream->writeByte(byte);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -1,27 +1,32 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include "DeflateElements.h"
|
||||||
|
|
||||||
#include "BitStream.h"
|
#include "BitStream.h"
|
||||||
|
|
||||||
|
class AbstractChecksumCalculator;
|
||||||
|
|
||||||
class DeflateBlock
|
class DeflateBlock
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
DeflateBlock(BitStream* inputStream, BitStream* outputStream);
|
DeflateBlock(BitStream* inputStream, BitStream* outputStream);
|
||||||
|
|
||||||
|
void buildCodeLengthMapping();
|
||||||
|
|
||||||
|
std::string getMetaData() const;
|
||||||
|
|
||||||
|
void flushToStream();
|
||||||
|
|
||||||
|
bool isFinalBlock() const;
|
||||||
|
|
||||||
void readHeader();
|
void readHeader();
|
||||||
|
|
||||||
void readDynamicHuffmanTable();
|
void readDynamicHuffmanTable();
|
||||||
|
|
||||||
void buildCodeLengthMapping();
|
|
||||||
|
|
||||||
void readLiteralCodeLengths();
|
void readLiteralCodeLengths();
|
||||||
|
|
||||||
bool readNextCodeLengthSymbol(unsigned char& buffer);
|
bool readNextCodeLengthSymbol(unsigned char& buffer);
|
||||||
|
|
||||||
bool isFinalBlock() const
|
|
||||||
{
|
|
||||||
return mInFinalBlock;
|
|
||||||
}
|
|
||||||
|
|
||||||
void setCodeLengthAlphabetLengths(const std::vector<unsigned char>& lengths);
|
void setCodeLengthAlphabetLengths(const std::vector<unsigned char>& lengths);
|
||||||
|
|
||||||
void setCodeLengthLength(unsigned length);
|
void setCodeLengthLength(unsigned length);
|
||||||
|
@ -32,7 +37,7 @@ public:
|
||||||
|
|
||||||
void setIsFinalBlock(bool isFinal);
|
void setIsFinalBlock(bool isFinal);
|
||||||
|
|
||||||
void flushToStream();
|
void write(uint16_t datalength);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
BitStream* mInputStream;
|
BitStream* mInputStream;
|
||||||
|
@ -49,12 +54,6 @@ private:
|
||||||
std::vector<unsigned char> mCodeLengthAlphabetLengths;
|
std::vector<unsigned char> mCodeLengthAlphabetLengths;
|
||||||
static constexpr unsigned CODE_LENGTH_ALPHABET_PERMUTATION[19]{16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15};
|
static constexpr unsigned CODE_LENGTH_ALPHABET_PERMUTATION[19]{16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15};
|
||||||
|
|
||||||
unsigned char mCompressionType{0};
|
|
||||||
|
|
||||||
bool mInFinalBlock{false};
|
bool mInFinalBlock{false};
|
||||||
|
Deflate::CompressionMethod mCompressionMethod{Deflate::CompressionMethod::NONE};
|
||||||
static constexpr unsigned char NO_COMPRESSION = 0x00;
|
|
||||||
static constexpr unsigned char FIXED_HUFFMAN = 0x01;
|
|
||||||
static constexpr unsigned char DYNAMIC_HUFFMAN = 0x02;
|
|
||||||
static constexpr unsigned char ERROR = 0x03;
|
|
||||||
};
|
};
|
33
src/compression/deflate/DeflateElements.h
Normal file
33
src/compression/deflate/DeflateElements.h
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
namespace Deflate
|
||||||
|
{
|
||||||
|
enum class CompressionMethod
|
||||||
|
{
|
||||||
|
NONE,
|
||||||
|
FIXED_HUFFMAN,
|
||||||
|
DYNAMIC_HUFFMAN,
|
||||||
|
ERROR
|
||||||
|
};
|
||||||
|
|
||||||
|
inline std::string toString(CompressionMethod method)
|
||||||
|
{
|
||||||
|
switch (method)
|
||||||
|
{
|
||||||
|
case CompressionMethod::NONE:
|
||||||
|
return "NONE";
|
||||||
|
case CompressionMethod::FIXED_HUFFMAN:
|
||||||
|
return "FIXED_HUFFMAN";
|
||||||
|
case CompressionMethod::DYNAMIC_HUFFMAN:
|
||||||
|
return "DYNAMIC_HUFFMAN";
|
||||||
|
case CompressionMethod::ERROR:
|
||||||
|
return "ERROR";
|
||||||
|
default:
|
||||||
|
return "UNKNOWN";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
78
src/compression/deflate/DeflateEncoder.cpp
Normal file
78
src/compression/deflate/DeflateEncoder.cpp
Normal file
|
@ -0,0 +1,78 @@
|
||||||
|
#include "DeflateEncoder.h"
|
||||||
|
|
||||||
|
#include "BitStream.h"
|
||||||
|
#include "DeflateBlock.h"
|
||||||
|
#include "BufferBitStream.h"
|
||||||
|
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
DeflateEncoder::DeflateEncoder(BitStream* inputStream, BitStream* outputStream)
|
||||||
|
: AbstractEncoder(inputStream, outputStream)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
DeflateEncoder::~DeflateEncoder()
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
bool DeflateEncoder::encode()
|
||||||
|
{
|
||||||
|
uint16_t count = 0;
|
||||||
|
BufferBitStream stream;
|
||||||
|
std::unique_ptr<DeflateBlock> working_block = std::make_unique<DeflateBlock>(&stream, mOutputStream);
|
||||||
|
|
||||||
|
AbstractChecksumCalculator* checksum_calc;
|
||||||
|
if (mChecksumCalculators.size() > 0)
|
||||||
|
{
|
||||||
|
std::cout << "Setting checksum calculator " << std::endl;
|
||||||
|
mOutputStream->setChecksumCalculator(mChecksumCalculators[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
while(true)
|
||||||
|
{
|
||||||
|
if (count == mMaxBlockSize)
|
||||||
|
{
|
||||||
|
std::cout << working_block->getMetaData();
|
||||||
|
working_block->write(count);
|
||||||
|
|
||||||
|
working_block = std::make_unique<DeflateBlock>(&stream, mOutputStream);
|
||||||
|
stream.reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (auto byte = mInputStream->readNextByte())
|
||||||
|
{
|
||||||
|
stream.writeByte(*byte);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
stream.resetOffsets();
|
||||||
|
working_block->setIsFinalBlock(true);
|
||||||
|
|
||||||
|
std::cout << working_block->getMetaData();
|
||||||
|
working_block->write(count);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
|
||||||
|
mOutputStream->clearChecksumCalculator();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool DeflateEncoder::decode()
|
||||||
|
{
|
||||||
|
auto working_block = std::make_unique<DeflateBlock>(mInputStream, mOutputStream);
|
||||||
|
working_block->readHeader();
|
||||||
|
|
||||||
|
DeflateBlock* raw_block = working_block.get();
|
||||||
|
|
||||||
|
while(!raw_block->isFinalBlock())
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "AbstractEncoder.h"
|
#include "AbstractEncoder.h"
|
||||||
|
#include "DeflateElements.h"
|
||||||
|
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
@ -18,8 +19,15 @@ public:
|
||||||
|
|
||||||
bool decode() override;
|
bool decode() override;
|
||||||
|
|
||||||
|
void setCompressionMethod(Deflate::CompressionMethod method)
|
||||||
|
{
|
||||||
|
mCompressionMethod = method;
|
||||||
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
std::vector<std::unique_ptr<DeflateBlock > > mBlocks;
|
uint16_t mMaxBlockSize{65535};
|
||||||
|
Deflate::CompressionMethod mCompressionMethod{Deflate::CompressionMethod::NONE};
|
||||||
|
std::unique_ptr<DeflateBlock > mLastBlock;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
16
src/core/streams/AbstractChecksumCalculator.h
Normal file
16
src/core/streams/AbstractChecksumCalculator.h
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "stdint.h"
|
||||||
|
|
||||||
|
class AbstractChecksumCalculator
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
virtual ~AbstractChecksumCalculator() = default;
|
||||||
|
|
||||||
|
virtual void addValue(unsigned char val) = 0;
|
||||||
|
virtual uint32_t getChecksum() const = 0;
|
||||||
|
|
||||||
|
virtual void reset()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
};
|
|
@ -28,6 +28,15 @@ void BitStream::write(uint32_t data)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void BitStream::writeWord(uint16_t data)
|
||||||
|
{
|
||||||
|
unsigned num_bytes = sizeof(uint16_t);
|
||||||
|
for(unsigned idx=0; idx<num_bytes;idx++)
|
||||||
|
{
|
||||||
|
writeByte(ByteUtils::getByteN(data, idx));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
int BitStream::getCurrentByteOffset() const
|
int BitStream::getCurrentByteOffset() const
|
||||||
{
|
{
|
||||||
return mByteOffset;
|
return mByteOffset;
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include "AbstractChecksumCalculator.h"
|
||||||
|
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <optional>
|
#include <optional>
|
||||||
|
@ -29,11 +31,39 @@ public:
|
||||||
|
|
||||||
void write(uint32_t data);
|
void write(uint32_t data);
|
||||||
|
|
||||||
|
void writeWord(uint16_t data);
|
||||||
|
|
||||||
virtual void writeBytes(const std::vector<unsigned char> data) = 0;
|
virtual void writeBytes(const std::vector<unsigned char> data) = 0;
|
||||||
|
|
||||||
|
void resetOffsets()
|
||||||
|
{
|
||||||
|
mByteOffset = -1;
|
||||||
|
mBitOffset = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual void reset()
|
||||||
|
{
|
||||||
|
resetOffsets();
|
||||||
|
mCurrentByte = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void setChecksumCalculator(AbstractChecksumCalculator* calc)
|
||||||
|
{
|
||||||
|
mChecksumCalculator = calc;
|
||||||
|
}
|
||||||
|
|
||||||
|
void clearChecksumCalculator()
|
||||||
|
{
|
||||||
|
mChecksumCalculator = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
//unsigned getSize() = 0;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
int mByteOffset{0};
|
int mByteOffset{-1};
|
||||||
unsigned mBitOffset{0};
|
unsigned mBitOffset{0};
|
||||||
|
|
||||||
unsigned char mCurrentByte{0};
|
unsigned char mCurrentByte{0};
|
||||||
|
|
||||||
|
AbstractChecksumCalculator* mChecksumCalculator{nullptr};
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,8 +1,10 @@
|
||||||
#include "BufferBitStream.h"
|
#include "BufferBitStream.h"
|
||||||
|
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
bool BufferBitStream::isFinished() const
|
bool BufferBitStream::isFinished() const
|
||||||
{
|
{
|
||||||
return mByteOffset == mBuffer.size();
|
return mByteOffset == mBuffer.size() - 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<unsigned char> BufferBitStream::peekNextNBytes(unsigned n) const
|
std::vector<unsigned char> BufferBitStream::peekNextNBytes(unsigned n) const
|
||||||
|
@ -43,6 +45,10 @@ void BufferBitStream::setBuffer(const std::vector<unsigned char>& data)
|
||||||
|
|
||||||
void BufferBitStream::writeByte(unsigned char data)
|
void BufferBitStream::writeByte(unsigned char data)
|
||||||
{
|
{
|
||||||
|
if (mChecksumCalculator)
|
||||||
|
{
|
||||||
|
mChecksumCalculator->addValue(data);
|
||||||
|
}
|
||||||
mBuffer.push_back(data);
|
mBuffer.push_back(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -27,12 +27,13 @@ public:
|
||||||
return mBuffer;
|
return mBuffer;
|
||||||
}
|
}
|
||||||
|
|
||||||
void resetOffsets()
|
void reset() override
|
||||||
{
|
{
|
||||||
mByteOffset = 0;
|
BitStream::reset();
|
||||||
mBitOffset = 0;
|
mBuffer.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
unsigned mBufferSize{0};
|
||||||
std::vector<unsigned char> mBuffer;
|
std::vector<unsigned char> mBuffer;
|
||||||
};
|
};
|
||||||
|
|
|
@ -8,6 +8,7 @@ list(APPEND image_LIB_INCLUDES
|
||||||
png/PngWriter.cpp
|
png/PngWriter.cpp
|
||||||
png/PngReader.cpp
|
png/PngReader.cpp
|
||||||
png/PngHeader.cpp
|
png/PngHeader.cpp
|
||||||
|
png/PngInfo.cpp
|
||||||
ImageBitStream.cpp
|
ImageBitStream.cpp
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -9,7 +9,7 @@ ImageBitStream::ImageBitStream(Image<unsigned char>* image)
|
||||||
|
|
||||||
bool ImageBitStream::isFinished() const
|
bool ImageBitStream::isFinished() const
|
||||||
{
|
{
|
||||||
return true;
|
return mByteOffset == mImage->getDataRef().size();
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<unsigned char> ImageBitStream::peekNextNBytes(unsigned n) const
|
std::vector<unsigned char> ImageBitStream::peekNextNBytes(unsigned n) const
|
||||||
|
@ -18,9 +18,16 @@ std::vector<unsigned char> ImageBitStream::peekNextNBytes(unsigned n) const
|
||||||
}
|
}
|
||||||
|
|
||||||
std::optional<unsigned char> ImageBitStream::readNextByte()
|
std::optional<unsigned char> ImageBitStream::readNextByte()
|
||||||
|
{
|
||||||
|
mByteOffset++;
|
||||||
|
|
||||||
|
if (isFinished() )
|
||||||
{
|
{
|
||||||
return std::nullopt;
|
return std::nullopt;
|
||||||
}
|
}
|
||||||
|
const auto val = mImage->getDataRef()[mByteOffset];
|
||||||
|
return val;
|
||||||
|
}
|
||||||
|
|
||||||
void ImageBitStream::writeByte(unsigned char data)
|
void ImageBitStream::writeByte(unsigned char data)
|
||||||
{
|
{
|
||||||
|
|
|
@ -22,6 +22,11 @@ public:
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
unsigned getBytesPerScanline() const
|
||||||
|
{
|
||||||
|
return mImage->getBytesPerRow();
|
||||||
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Image<unsigned char>* mImage{nullptr};
|
Image<unsigned char>* mImage{nullptr};
|
||||||
};
|
};
|
||||||
|
|
73
src/image/png/PngFilter.h
Normal file
73
src/image/png/PngFilter.h
Normal file
|
@ -0,0 +1,73 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "ImageBitStream.h"
|
||||||
|
#include "BitStream.h"
|
||||||
|
|
||||||
|
#include "FileLogger.h"
|
||||||
|
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
class PngFilter
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
enum class FilterType : unsigned char
|
||||||
|
{
|
||||||
|
NONE,
|
||||||
|
SUB,
|
||||||
|
UP,
|
||||||
|
AVERAGE,
|
||||||
|
PAETH
|
||||||
|
};
|
||||||
|
|
||||||
|
PngFilter(BitStream* inputStream, BitStream* outputStream)
|
||||||
|
: mInputStream(inputStream),
|
||||||
|
mOutputStream(outputStream)
|
||||||
|
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void encode()
|
||||||
|
{
|
||||||
|
auto image_stream = dynamic_cast<ImageBitStream*>(mInputStream);
|
||||||
|
if (!image_stream)
|
||||||
|
{
|
||||||
|
MLOG_ERROR("Expected ImageStream in PngFilter encode - aborting.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto bytes_per_scanline = image_stream->getBytesPerScanline();
|
||||||
|
unsigned count{0};
|
||||||
|
if (mFilterType == FilterType::NONE)
|
||||||
|
{
|
||||||
|
while(true)
|
||||||
|
{
|
||||||
|
if (const auto byte = image_stream->readNextByte())
|
||||||
|
{
|
||||||
|
if (count % bytes_per_scanline == 0)
|
||||||
|
{
|
||||||
|
mOutputStream->writeByte(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
mOutputStream->writeByte(*byte);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void decode()
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
FilterType mFilterType{FilterType::NONE};
|
||||||
|
BitStream* mInputStream{nullptr};
|
||||||
|
BitStream* mOutputStream{nullptr};
|
||||||
|
};
|
|
@ -37,7 +37,7 @@ std::string PngHeader::getFileName() const
|
||||||
|
|
||||||
const std::string& PngHeader::getChunkName() const
|
const std::string& PngHeader::getChunkName() const
|
||||||
{
|
{
|
||||||
return "IHDR";
|
return mName;
|
||||||
}
|
}
|
||||||
|
|
||||||
const std::vector<unsigned char>& PngHeader::getData() const
|
const std::vector<unsigned char>& PngHeader::getData() const
|
||||||
|
@ -61,9 +61,9 @@ void PngHeader::updateData()
|
||||||
}
|
}
|
||||||
mData.push_back(mBitDepth);
|
mData.push_back(mBitDepth);
|
||||||
mData.push_back(static_cast<unsigned char>(mPngInfo.mColorType));
|
mData.push_back(static_cast<unsigned char>(mPngInfo.mColorType));
|
||||||
mData.push_back(mPngInfo.mCompressionMethod);
|
mData.push_back(static_cast<unsigned char>(mPngInfo.mCompressionMethod));
|
||||||
mData.push_back(mPngInfo.mFilterMethod);
|
mData.push_back(static_cast<unsigned char>(mPngInfo.mFilterMethod));
|
||||||
mData.push_back(mPngInfo.mInterlaceMethod);
|
mData.push_back(static_cast<unsigned char>(mPngInfo.mInterlaceMethod));
|
||||||
}
|
}
|
||||||
|
|
||||||
uint32_t PngHeader::getCrc() const
|
uint32_t PngHeader::getCrc() const
|
||||||
|
|
95
src/image/png/PngInfo.cpp
Normal file
95
src/image/png/PngInfo.cpp
Normal file
|
@ -0,0 +1,95 @@
|
||||||
|
#include "PngInfo.h"
|
||||||
|
|
||||||
|
std::string PngInfo::toString(ColorType colorType) const
|
||||||
|
{
|
||||||
|
switch(colorType)
|
||||||
|
{
|
||||||
|
case ColorType::GREYSCALE:
|
||||||
|
return "GREYSCALE";
|
||||||
|
case ColorType::RGB:
|
||||||
|
return "RGB";
|
||||||
|
case ColorType::PALETTE:
|
||||||
|
return "PALETTE";
|
||||||
|
case ColorType::GREYSCALE_ALPHA:
|
||||||
|
return "GREYSCALE_ALPHA";
|
||||||
|
case ColorType::RGB_ALPHA:
|
||||||
|
return "RGB_ALPHA";
|
||||||
|
default:
|
||||||
|
return "UNKNOWN";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string PngInfo::toString(CompressionMethod method) const
|
||||||
|
{
|
||||||
|
switch(method)
|
||||||
|
{
|
||||||
|
case CompressionMethod::DEFLATE:
|
||||||
|
return "DEFLATE";
|
||||||
|
default:
|
||||||
|
return "UNKNOWN";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string PngInfo::toString(FilterMethod method) const
|
||||||
|
{
|
||||||
|
switch(method)
|
||||||
|
{
|
||||||
|
case FilterMethod::ADAPTIVE:
|
||||||
|
return "ADAPTIVE";
|
||||||
|
default:
|
||||||
|
return "UNKNOWN";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string PngInfo::toString(InterlaceMethod method) const
|
||||||
|
{
|
||||||
|
switch(method)
|
||||||
|
{
|
||||||
|
case InterlaceMethod::NONE:
|
||||||
|
return "NONE";
|
||||||
|
case InterlaceMethod::ADAM7:
|
||||||
|
return "ADAM7";
|
||||||
|
default:
|
||||||
|
return "UNKNOWN";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool PngInfo::bitDepthIsValid(ColorType colorType, unsigned char bitDepth) const
|
||||||
|
{
|
||||||
|
switch(colorType)
|
||||||
|
{
|
||||||
|
case ColorType::GREYSCALE:
|
||||||
|
return (bitDepth == 1) || (bitDepth == 2) || (bitDepth == 4) || (bitDepth == 8) || (bitDepth == 16) ;
|
||||||
|
case ColorType::RGB:
|
||||||
|
return (bitDepth == 8) || (bitDepth == 16);
|
||||||
|
case ColorType::PALETTE:
|
||||||
|
return (bitDepth == 1) || (bitDepth == 2) || (bitDepth == 4) || (bitDepth == 8);
|
||||||
|
case ColorType::GREYSCALE_ALPHA:
|
||||||
|
return (bitDepth == 8) || (bitDepth == 16);
|
||||||
|
case ColorType::RGB_ALPHA:
|
||||||
|
return (bitDepth == 8) || (bitDepth == 16);
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool PngInfo::compressionMethodIsValid(unsigned char method)
|
||||||
|
{
|
||||||
|
return method == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool PngInfo::filterMethodIsValid(unsigned char method)
|
||||||
|
{
|
||||||
|
return method == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string PngInfo::toString() const
|
||||||
|
{
|
||||||
|
std::stringstream sstr;
|
||||||
|
sstr << "PngInfo" << "\n";
|
||||||
|
sstr << "colorType: " << toString(mColorType) << "\n";
|
||||||
|
sstr << "compressionMethod: " << toString(mCompressionMethod) << "\n";
|
||||||
|
sstr << "filterMethod: " << toString(mFilterMethod) << "\n";
|
||||||
|
sstr << "interlaceMethod: " << toString(mInterlaceMethod) << "\n";
|
||||||
|
return sstr.str();
|
||||||
|
}
|
|
@ -12,43 +12,45 @@ public:
|
||||||
RGB = 2,
|
RGB = 2,
|
||||||
PALETTE = 3,
|
PALETTE = 3,
|
||||||
GREYSCALE_ALPHA = 4,
|
GREYSCALE_ALPHA = 4,
|
||||||
RGB_ALPHA = 6,
|
RGB_ALPHA = 6
|
||||||
};
|
};
|
||||||
|
|
||||||
std::string toString(ColorType colorType) const
|
enum class CompressionMethod : unsigned char
|
||||||
{
|
{
|
||||||
switch(colorType)
|
DEFLATE = 0
|
||||||
{
|
};
|
||||||
case ColorType::GREYSCALE:
|
|
||||||
return "GREYSCALE";
|
|
||||||
case ColorType::RGB:
|
|
||||||
return "RGB";
|
|
||||||
case ColorType::PALETTE:
|
|
||||||
return "PALETTE";
|
|
||||||
case ColorType::GREYSCALE_ALPHA:
|
|
||||||
return "GREYSCALE_ALPHA";
|
|
||||||
case ColorType::RGB_ALPHA:
|
|
||||||
return "RGB_ALPHA";
|
|
||||||
default:
|
|
||||||
return "UNKNOWN";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string toString() const
|
enum class FilterMethod : unsigned char
|
||||||
{
|
{
|
||||||
std::stringstream sstr;
|
ADAPTIVE = 0
|
||||||
sstr << "PngInfo" << "\n";
|
};
|
||||||
sstr << "colorType: " << toString(mColorType) << "\n";
|
|
||||||
sstr << "compressionMethod: " << (int)mCompressionMethod << "\n";
|
enum class InterlaceMethod : unsigned char
|
||||||
sstr << "filterMethod: " << (int)mFilterMethod << "\n";
|
{
|
||||||
sstr << "interlaceMethod: " << (int)mInterlaceMethod << "\n";
|
NONE = 0,
|
||||||
return sstr.str();
|
ADAM7 = 1
|
||||||
}
|
};
|
||||||
|
|
||||||
|
std::string toString(ColorType colorType) const;
|
||||||
|
|
||||||
|
std::string toString(CompressionMethod method) const;
|
||||||
|
|
||||||
|
std::string toString(FilterMethod method) const;
|
||||||
|
|
||||||
|
std::string toString(InterlaceMethod method) const;
|
||||||
|
|
||||||
|
bool bitDepthIsValid(ColorType colorType, unsigned char bitDepth) const;
|
||||||
|
|
||||||
|
bool compressionMethodIsValid(unsigned char method);
|
||||||
|
|
||||||
|
bool filterMethodIsValid(unsigned char method);
|
||||||
|
|
||||||
|
std::string toString() const;
|
||||||
|
|
||||||
ColorType mColorType{ColorType::RGB};
|
ColorType mColorType{ColorType::RGB};
|
||||||
unsigned char mCompressionMethod{0};
|
CompressionMethod mCompressionMethod{CompressionMethod::DEFLATE};
|
||||||
unsigned char mFilterMethod{0};
|
FilterMethod mFilterMethod{FilterMethod::ADAPTIVE};
|
||||||
unsigned char mInterlaceMethod{0};
|
InterlaceMethod mInterlaceMethod{InterlaceMethod::NONE};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -85,25 +85,12 @@ bool PngReader::readChunk()
|
||||||
}
|
}
|
||||||
|
|
||||||
void PngReader::readIDATChunk(unsigned length)
|
void PngReader::readIDATChunk(unsigned length)
|
||||||
{
|
|
||||||
if (mAwaitingDataBlock)
|
|
||||||
{
|
|
||||||
mEncoder->setCompressionMethod(*mFile->readNextByte());
|
|
||||||
mEncoder->setExtraFlags(*mFile->readNextByte());
|
|
||||||
for(unsigned idx=0; idx<length-2; idx++)
|
|
||||||
{
|
|
||||||
mInputStream->writeByte(*mFile->readNextByte());
|
|
||||||
}
|
|
||||||
mAwaitingDataBlock = false;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
{
|
||||||
for(unsigned idx=0; idx<length; idx++)
|
for(unsigned idx=0; idx<length; idx++)
|
||||||
{
|
{
|
||||||
mInputStream->writeByte(*mFile->readNextByte());
|
mInputStream->writeByte(*mFile->readNextByte());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
void PngReader::readHeaderChunk()
|
void PngReader::readHeaderChunk()
|
||||||
{
|
{
|
||||||
|
@ -114,11 +101,10 @@ void PngReader::readHeaderChunk()
|
||||||
mHeader.setImageData(width, height, bitDepth);
|
mHeader.setImageData(width, height, bitDepth);
|
||||||
|
|
||||||
PngInfo info;
|
PngInfo info;
|
||||||
|
|
||||||
info.mColorType = static_cast<PngInfo::ColorType>(mFile->GetInHandle()->get());
|
info.mColorType = static_cast<PngInfo::ColorType>(mFile->GetInHandle()->get());
|
||||||
info.mCompressionMethod = mFile->GetInHandle()->get();
|
info.mCompressionMethod = static_cast<PngInfo::CompressionMethod>(mFile->GetInHandle()->get());
|
||||||
info.mFilterMethod = mFile->GetInHandle()->get();
|
info.mFilterMethod = static_cast<PngInfo::FilterMethod>(mFile->GetInHandle()->get());
|
||||||
info.mInterlaceMethod = mFile->GetInHandle()->get();
|
info.mInterlaceMethod = static_cast<PngInfo::InterlaceMethod>(mFile->GetInHandle()->get());
|
||||||
mHeader.setPngInfo(info);
|
mHeader.setPngInfo(info);
|
||||||
|
|
||||||
mCurrentOffset += 13;
|
mCurrentOffset += 13;
|
||||||
|
@ -152,6 +138,8 @@ std::unique_ptr<Image<unsigned char> > PngReader::read()
|
||||||
{
|
{
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::cout << mEncoder->getData() << std::endl;
|
||||||
mEncoder->decode();
|
mEncoder->decode();
|
||||||
return std::move(image);
|
return std::move(image);
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,13 +6,14 @@
|
||||||
#include "OutputBitStream.h"
|
#include "OutputBitStream.h"
|
||||||
#include "ImageBitStream.h"
|
#include "ImageBitStream.h"
|
||||||
|
|
||||||
|
#include "PngFilter.h"
|
||||||
#include "Lz77Encoder.h"
|
#include "Lz77Encoder.h"
|
||||||
#include "ZlibEncoder.h"
|
#include "ZlibEncoder.h"
|
||||||
#include "CyclicRedundancyChecker.h"
|
#include "CyclicRedundancyChecker.h"
|
||||||
|
|
||||||
#include "ByteUtils.h"
|
#include "ByteUtils.h"
|
||||||
|
|
||||||
#include <stdio.h>
|
#include <iostream>
|
||||||
|
|
||||||
PngWriter::PngWriter()
|
PngWriter::PngWriter()
|
||||||
{
|
{
|
||||||
|
@ -82,6 +83,8 @@ void PngWriter::writeHeader()
|
||||||
mPngHeader.updateData();
|
mPngHeader.updateData();
|
||||||
mOutStream->writeBytes(mPngHeader.getData());
|
mOutStream->writeBytes(mPngHeader.getData());
|
||||||
|
|
||||||
|
std::cout << "Writing header " << mPngHeader.toString() << std::endl;
|
||||||
|
|
||||||
auto crc = mPngHeader.getCrc();
|
auto crc = mPngHeader.getCrc();
|
||||||
mOutStream->write(crc);
|
mOutStream->write(crc);
|
||||||
}
|
}
|
||||||
|
@ -92,17 +95,24 @@ void PngWriter::writeEndChunk()
|
||||||
mOutStream->write(length);
|
mOutStream->write(length);
|
||||||
|
|
||||||
mOutStream->writeBytes(StringUtils::toBytes("IEND"));
|
mOutStream->writeBytes(StringUtils::toBytes("IEND"));
|
||||||
|
std::vector<unsigned char> char_data = StringUtils::toBytes("IEND");
|
||||||
|
|
||||||
CyclicRedundancyChecker crc_check;
|
CyclicRedundancyChecker crc_check;
|
||||||
auto crc = crc_check.doCrc(nullptr, 0);
|
auto crc = crc_check.doCrc(char_data.data(), char_data.size());
|
||||||
mOutStream->write(crc);
|
mOutStream->write(crc);
|
||||||
|
|
||||||
|
std::cout << "Writing end chunk" << std::endl;
|
||||||
}
|
}
|
||||||
|
|
||||||
void PngWriter::writeDataChunks(const BufferBitStream& buffer)
|
void PngWriter::writeDataChunks(const BufferBitStream& buffer)
|
||||||
{
|
{
|
||||||
auto num_bytes = buffer.getBuffer().size();
|
auto num_bytes = buffer.getBuffer().size();
|
||||||
auto max_bytes{32000};
|
auto max_bytes{32000};
|
||||||
std::vector<unsigned char> crc_buffer(max_bytes, 0);
|
std::vector<unsigned char> crc_buffer(num_bytes + 4, 0);
|
||||||
|
crc_buffer[0] = 'I';
|
||||||
|
crc_buffer[1] = 'D';
|
||||||
|
crc_buffer[2] = 'A';
|
||||||
|
crc_buffer[3] = 'T';
|
||||||
|
|
||||||
unsigned num_dat_chunks = num_bytes/max_bytes + 1;
|
unsigned num_dat_chunks = num_bytes/max_bytes + 1;
|
||||||
unsigned offset = 0;
|
unsigned offset = 0;
|
||||||
|
@ -114,19 +124,22 @@ void PngWriter::writeDataChunks(const BufferBitStream& buffer)
|
||||||
length = num_bytes - num_dat_chunks*num_bytes;
|
length = num_bytes - num_dat_chunks*num_bytes;
|
||||||
}
|
}
|
||||||
|
|
||||||
mOutStream->write(length);
|
std::cout << "Writing idat length " << num_bytes << std::endl;
|
||||||
|
mOutStream->write(num_bytes);
|
||||||
|
|
||||||
mOutStream->writeBytes(StringUtils::toBytes("IDAT"));
|
mOutStream->writeBytes(StringUtils::toBytes("IDAT"));
|
||||||
|
|
||||||
for(unsigned jdx=0; jdx<length; jdx++)
|
for(unsigned jdx=0; jdx<num_bytes; jdx++)
|
||||||
{
|
{
|
||||||
auto val = buffer.getBuffer()[idx*max_bytes + jdx];
|
auto val = buffer.getBuffer()[idx*max_bytes + jdx];
|
||||||
crc_buffer[jdx] = val;
|
crc_buffer[jdx + 4] = val;
|
||||||
mOutStream->writeByte(val);
|
mOutStream->writeByte(val);
|
||||||
}
|
}
|
||||||
|
|
||||||
CyclicRedundancyChecker crc_check;
|
CyclicRedundancyChecker crc_check;
|
||||||
auto crc = crc_check.doCrc(crc_buffer.data(), crc_buffer.size());
|
auto crc = crc_check.doCrc(crc_buffer.data(), crc_buffer.size());
|
||||||
|
|
||||||
|
std::cout << "Writing idat crc" << crc << std::endl;
|
||||||
mOutStream->write(crc);
|
mOutStream->write(crc);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -146,18 +159,41 @@ void PngWriter::write(const std::unique_ptr<Image<unsigned char> >& image)
|
||||||
}
|
}
|
||||||
|
|
||||||
mWorkingImage = image.get();
|
mWorkingImage = image.get();
|
||||||
mInStream = std::make_unique<ImageBitStream>(image.get());
|
|
||||||
|
auto image_bit_stream = std::make_unique<ImageBitStream>(image.get());
|
||||||
|
auto raw_image_stream = image_bit_stream.get();
|
||||||
|
|
||||||
|
mInStream = std::move(image_bit_stream);
|
||||||
|
|
||||||
writeHeader();
|
writeHeader();
|
||||||
|
|
||||||
BufferBitStream lz77_out_stream;
|
auto filter_out_stream = std::make_unique<BufferBitStream>();
|
||||||
Lz77Encoder lz77_encoder(mInStream.get(), &lz77_out_stream);
|
PngFilter filter(raw_image_stream, filter_out_stream.get());
|
||||||
|
filter.encode();
|
||||||
|
//while(!filter_out_stream->isFinished())
|
||||||
|
//{
|
||||||
|
//std::cout << "Got pix " << static_cast<int>(*filter_out_stream->readNextByte()) << std::endl;
|
||||||
|
//}
|
||||||
|
|
||||||
|
filter_out_stream->resetOffsets();
|
||||||
|
|
||||||
|
std::unique_ptr<BufferBitStream> lz77_out_stream;
|
||||||
|
|
||||||
|
if (mCompressionMethod == Deflate::CompressionMethod::NONE)
|
||||||
|
{
|
||||||
|
lz77_out_stream = std::move(filter_out_stream);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
lz77_out_stream = std::make_unique<BufferBitStream>();
|
||||||
|
Lz77Encoder lz77_encoder(filter_out_stream.get(), lz77_out_stream.get());
|
||||||
|
|
||||||
lz77_encoder.encode();
|
lz77_encoder.encode();
|
||||||
lz77_out_stream.resetOffsets();
|
lz77_out_stream->resetOffsets();
|
||||||
|
}
|
||||||
|
|
||||||
BufferBitStream zlib_out_stream;
|
BufferBitStream zlib_out_stream;
|
||||||
ZlibEncoder zlib_encoder(&lz77_out_stream, &zlib_out_stream);
|
ZlibEncoder zlib_encoder(lz77_out_stream.get(), &zlib_out_stream);
|
||||||
zlib_encoder.encode();
|
zlib_encoder.encode();
|
||||||
zlib_out_stream.resetOffsets();
|
zlib_out_stream.resetOffsets();
|
||||||
|
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
|
|
||||||
#include "PngHeader.h"
|
#include "PngHeader.h"
|
||||||
#include "Image.h"
|
#include "Image.h"
|
||||||
|
#include "DeflateElements.h"
|
||||||
|
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
@ -46,6 +47,8 @@ private:
|
||||||
unsigned mPngInfoUserSet{false};
|
unsigned mPngInfoUserSet{false};
|
||||||
PngInfo mPngInfo;
|
PngInfo mPngInfo;
|
||||||
PngHeader mPngHeader;
|
PngHeader mPngHeader;
|
||||||
|
|
||||||
|
Deflate::CompressionMethod mCompressionMethod{Deflate::CompressionMethod::NONE};
|
||||||
};
|
};
|
||||||
|
|
||||||
using PngWriterPtr = std::unique_ptr<PngWriter>;
|
using PngWriterPtr = std::unique_ptr<PngWriter>;
|
||||||
|
|
|
@ -9,21 +9,26 @@
|
||||||
|
|
||||||
int main()
|
int main()
|
||||||
{
|
{
|
||||||
unsigned width = 20;
|
unsigned width = 10;
|
||||||
unsigned height = 20;
|
unsigned height = 10;
|
||||||
unsigned numChannels = 3;
|
unsigned numChannels = 1;
|
||||||
auto image = Image<unsigned char>::Create(width, height);
|
auto image = Image<unsigned char>::Create(width, height);
|
||||||
image->setNumChannels(numChannels);
|
image->setNumChannels(numChannels);
|
||||||
|
image->setBitDepth(8);
|
||||||
|
|
||||||
std::vector<unsigned char> data(image->getBytesPerRow()*height, 0);
|
std::vector<unsigned char> data(width*height, 0);
|
||||||
|
for (unsigned idx=0; idx<width*height; idx++)
|
||||||
|
{
|
||||||
|
data[idx] = 10;
|
||||||
|
}
|
||||||
|
|
||||||
ImagePrimitives::drawAlternatingStrips(data, width, height, numChannels, image->getBytesPerRow());
|
|
||||||
image->setData(data);
|
image->setData(data);
|
||||||
|
|
||||||
PngWriter writer;
|
PngWriter writer;
|
||||||
writer.setPath("test.png");
|
writer.setPath("test.png");
|
||||||
writer.write(image);
|
writer.write(image);
|
||||||
|
|
||||||
|
return 0;
|
||||||
File test_file("test.png");
|
File test_file("test.png");
|
||||||
test_file.SetAccessMode(File::AccessMode::Read);
|
test_file.SetAccessMode(File::AccessMode::Read);
|
||||||
test_file.Open(true);
|
test_file.Open(true);
|
||||||
|
|
Loading…
Reference in a new issue