/*
|
|
cmake_minimum_required(VERSION 3.27)
|
|
project(cordiceps)
|
|
|
|
set(CMAKE_CXX_STANDARD 23)
|
|
|
|
include(FetchContent)
|
|
|
|
FetchContent_Declare(
|
|
raylib
|
|
GIT_REPOSITORY https://github.com/raysan5/raylib.git
|
|
GIT_TAG 5.0
|
|
)
|
|
|
|
FetchContent_MakeAvailable(raylib)
|
|
|
|
add_executable(cordiceps cordiceps.cpp)
|
|
target_link_libraries(cordiceps PUBLIC raylib_static)
|
|
*/
|
|
|
|
#include <ranges>
|
|
#include <iostream>
|
|
#include <fstream>
|
|
#include <sstream>
|
|
#include <vector>
|
|
#include <cassert>
|
|
#include <cstdint>
|
|
#include <bit>
|
|
#include <bitset>
|
|
#include "raylib.h"
|
|
|
|
std::string slurp(const char* filename) {
|
|
std::ifstream file{filename};
|
|
std::stringstream data;
|
|
file >> data.rdbuf();
|
|
return data.str();
|
|
}
|
|
|
|
std::vector<bool> to_bits(std::string str) {
|
|
std::vector<bool> d;
|
|
for(char c : str) {
|
|
d.push_back(c & 0b10000000);
|
|
d.push_back(c & 0b1000000);
|
|
d.push_back(c & 0b100000);
|
|
d.push_back(c & 0b10000);
|
|
d.push_back(c & 0b1000);
|
|
d.push_back(c & 0b100);
|
|
d.push_back(c & 0b10);
|
|
d.push_back(c & 0b1);
|
|
}
|
|
return d;
|
|
}
|
|
|
|
std::string from_bits(std::vector<bool> data) {
|
|
std::string d;
|
|
std::bitset<32> size_bits;
|
|
for(int idx = 0; idx < 32; ++idx) size_bits[idx] = data[idx];
|
|
uint32_t size = size_bits.to_ulong();
|
|
if(std::endian::native == std::endian::little) size = std::byteswap(size);
|
|
|
|
for(auto composite : data | std::views::drop(32) | std::views::chunk(8)) {
|
|
char c = 0;
|
|
for(auto b : composite) {
|
|
c *= 2;
|
|
c += b;
|
|
}
|
|
d.push_back(c);
|
|
size--;
|
|
if(size == 0) return d;
|
|
}
|
|
return d;
|
|
}
|
|
|
|
void encode(const char* pic, const char* data_source) {
|
|
auto image = LoadImage(pic);
|
|
auto raw_data = slurp(data_source);
|
|
uint32_t size = raw_data.size();
|
|
if(std::endian::native == std::endian::little) size = std::byteswap(size);
|
|
std::bitset<32> size_bits;
|
|
size_bits = size;
|
|
std::vector<bool> raw_bits = to_bits(raw_data);
|
|
assert(raw_bits.size() % 8 == 0);
|
|
assert(raw_bits.size() / 8 == raw_data.size());
|
|
std::vector<bool> data;
|
|
|
|
for(int idx = 0; idx < 32; ++idx) data.push_back(size_bits[idx]);
|
|
data.insert(data.end(), raw_bits.begin(), raw_bits.end());
|
|
|
|
size_t idx = 0;
|
|
for(auto j : std::ranges::iota_view(0, image.height)) {
|
|
for(auto i : std::ranges::iota_view(0, image.width)) {
|
|
auto c = GetImageColor(image, i, j);
|
|
c.r ^= c.r & 1;
|
|
c.g ^= c.g & 1;
|
|
c.b ^= c.b & 1;
|
|
c.r += idx < data.size() ? data[idx] : 0; idx++;
|
|
c.g += idx < data.size() ? data[idx] : 0; idx++;
|
|
c.b += idx < data.size() ? data[idx] : 0; idx++;
|
|
ImageDrawPixel(&image, i, j, c);
|
|
}
|
|
}
|
|
ExportImage(image, pic);
|
|
UnloadImage(image);
|
|
if(raw_bits.size() > idx) {
|
|
std::cout << "\x1b[1;31mWritten bits: " << data.size() << "/" << idx << std::endl;
|
|
} else {
|
|
std::cout << "\x1b[1;32mWritten bits: " << data.size() << "/" << idx << std::endl;
|
|
}
|
|
}
|
|
|
|
void decode(const char* pic) {
|
|
auto image = LoadImage(pic);
|
|
std::vector<bool> raw_bits;
|
|
size_t idx = 0;
|
|
for(auto j : std::ranges::iota_view(0, image.height)) {
|
|
for(auto i : std::ranges::iota_view(0, image.width)) {
|
|
auto c = GetImageColor(image, i, j);
|
|
raw_bits.push_back(c.r & 1);
|
|
raw_bits.push_back(c.g & 1);
|
|
raw_bits.push_back(c.b & 1);
|
|
}
|
|
}
|
|
UnloadImage(image);
|
|
std::cout << from_bits(raw_bits);
|
|
}
|
|
|
|
int main(int argc, char** argv) {
|
|
if(argc <= 1) {
|
|
perror("arguments missing, expected: picture then datafile for coding, picture for decoding");
|
|
}
|
|
if(argc == 3) {
|
|
encode(argv[1], argv[2]);
|
|
} else if(argc == 2) {
|
|
decode(argv[1]);
|
|
}
|
|
return 0;
|
|
}
|