kopia lustrzana https://github.com/joshua-jerred/SSTV-Image-Tools
Initial Commit
commit
b0758dcc73
|
@ -0,0 +1,2 @@
|
||||||
|
build
|
||||||
|
.vscode
|
|
@ -0,0 +1 @@
|
||||||
|
{"total":15240,"sessions":[{"begin":"2023-03-12T13:44:37-06:00","end":"2023-03-12T13:48:55-06:00","duration":258},{"begin":"2023-03-12T13:49:38-06:00","end":"2023-03-12T13:55:19-06:00","duration":341},{"begin":"2023-03-12T13:55:28-06:00","end":"2023-03-12T14:12:39-06:00","duration":1030},{"begin":"2023-03-12T14:12:42-06:00","end":"2023-03-12T14:53:07-06:00","duration":2424},{"begin":"2023-03-12T14:56:47-06:00","end":"2023-03-12T15:03:52-06:00","duration":425},{"begin":"2023-03-12T15:05:24-06:00","end":"2023-03-12T15:32:37-06:00","duration":1632},{"begin":"2023-03-12T15:32:42-06:00","end":"2023-03-12T15:48:18-06:00","duration":936},{"begin":"2023-03-12T15:49:19-06:00","end":"2023-03-12T15:59:42-06:00","duration":623},{"begin":"2023-03-12T16:00:06-06:00","end":"2023-03-12T16:07:04-06:00","duration":417},{"begin":"2023-03-12T16:07:33-06:00","end":"2023-03-12T16:13:28-06:00","duration":354},{"begin":"2023-03-12T16:17:33-06:00","end":"2023-03-12T16:36:24-06:00","duration":1131},{"begin":"2023-03-12T16:38:20-06:00","end":"2023-03-12T17:24:24-06:00","duration":2764},{"begin":"2023-03-12T17:25:18-06:00","end":"2023-03-12T17:27:19-06:00","duration":121},{"begin":"2023-03-12T18:08:42-06:00","end":"2023-03-12T18:08:57-06:00","duration":15},{"begin":"2023-03-12T18:09:00-06:00","end":"2023-03-12T18:11:02-06:00","duration":122},{"begin":"2023-03-12T19:14:25-06:00","end":"2023-03-12T19:17:15-06:00","duration":170},{"begin":"2023-03-12T19:18:33-06:00","end":"2023-03-12T19:20:50-06:00","duration":137},{"begin":"2023-03-12T19:21:10-06:00","end":"2023-03-12T19:24:03-06:00","duration":173},{"begin":"2023-03-12T19:25:08-06:00","end":"2023-03-12T19:33:11-06:00","duration":483},{"begin":"2023-03-12T19:35:37-06:00","end":"2023-03-12T19:37:53-06:00","duration":135},{"begin":"2023-03-12T19:39:51-06:00","end":"2023-03-12T19:41:53-06:00","duration":122},{"begin":"2023-03-12T19:48:10-06:00","end":"2023-03-12T20:11:58-06:00","duration":1427}]}
|
|
@ -0,0 +1,25 @@
|
||||||
|
cmake_minimum_required(VERSION 3.14)
|
||||||
|
project(SSTV-Image-Tools VERSION 0.4)
|
||||||
|
set(CMAKE_CXX_STANDARD 20)
|
||||||
|
|
||||||
|
set(CMAKE_BUILD_TYPE Debug) # Change to Release for production
|
||||||
|
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} \
|
||||||
|
-fno-omit-frame-pointer \
|
||||||
|
-pedantic \
|
||||||
|
-Wall \
|
||||||
|
-Wextra \
|
||||||
|
-Weffc++ \
|
||||||
|
-Wdisabled-optimization \
|
||||||
|
-Wno-unused-variable")
|
||||||
|
|
||||||
|
include_directories(src)
|
||||||
|
|
||||||
|
add_executable(example
|
||||||
|
example/example.cpp
|
||||||
|
src/sstv-image-tools.cpp
|
||||||
|
)
|
||||||
|
add_definitions(-DMAGICKCORE_QUANTUM_DEPTH=8)
|
||||||
|
add_definitions(-DMAGICKCORE_HDRI_ENABLE=1) # Required or there are linking errors with Magick::Color::Color
|
||||||
|
find_package(ImageMagick COMPONENTS Magick++)
|
||||||
|
include_directories(${ImageMagick_INCLUDE_DIRS})
|
||||||
|
target_link_libraries(example ${ImageMagick_LIBRARIES})
|
|
@ -0,0 +1,33 @@
|
||||||
|
# SSTV Image Tools C++ Library
|
||||||
|
|
||||||
|
Currently in development but it's functional. Will be used with the MWAV library to create SSTV images.
|
||||||
|
|
||||||
|
A simple library for manipulating images into common SSTV image formats using Magick++.
|
||||||
|
|
||||||
|
Basic Features:
|
||||||
|
- Overlay call sign and ~~message text~~
|
||||||
|
- Convert the image to the proper color space
|
||||||
|
- Get the values for individual pixels
|
||||||
|
- ~~Add Data to the image~~
|
||||||
|
|
||||||
|
***
|
||||||
|
|
||||||
|
Supported SSTV Image Formats:
|
||||||
|
- Robot24
|
||||||
|
- Robot36
|
||||||
|
- Robot72
|
||||||
|
- ~~Robot8 B/W~~
|
||||||
|
- ~~Robot16 B/W~~
|
||||||
|
|
||||||
|
Tested with jpeg, and png images, support for other Magick++ supported formats has not been tested.
|
||||||
|
|
||||||
|
Magick++ (7.1) with ``libjpeg62-dev``, ``libpng-dev``, ``libfreetype6-dev`` is required.
|
||||||
|
|
||||||
|
***
|
||||||
|
|
||||||
|
Example Image Sources:
|
||||||
|
|Image File|Source|License|
|
||||||
|
|:--------:|:----:|:-----:|
|
||||||
|
|`example/test1.png`|[Wikipedia](https://en.wikipedia.org/wiki/File:Philips_PM5544.svg)|GNU 1.2|
|
||||||
|
|`example/test2.png`|[Wikipedia](https://en.wikipedia.org/wiki/File:TwibrightLinksTestCard.png)|GNU 1.2|
|
||||||
|
|`example/test3.jpg`|[Wikipedia](https://en.wikipedia.org/wiki/File:SMPTE_Color_Bars.svg)|Public Domain|
|
Plik binarny nie jest wyświetlany.
Po Szerokość: | Wysokość: | Rozmiar: 24 KiB |
Plik binarny nie jest wyświetlany.
Po Szerokość: | Wysokość: | Rozmiar: 25 KiB |
Plik binarny nie jest wyświetlany.
Po Szerokość: | Wysokość: | Rozmiar: 6.2 KiB |
|
@ -0,0 +1,27 @@
|
||||||
|
#include <array>
|
||||||
|
#include <iostream>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
#include "sstv-image-tools.h"
|
||||||
|
|
||||||
|
int main() {
|
||||||
|
std::array<std::string, 3> test_images = {"test1.png", "test2.png",
|
||||||
|
"test3.jpg"};
|
||||||
|
|
||||||
|
for (auto &image_path : test_images) {
|
||||||
|
SstvImage image(SstvImage::Mode::ROBOT_36_COLOR, image_path,
|
||||||
|
"converted_" + image_path);
|
||||||
|
image.AddCallSign("N0CALL");
|
||||||
|
|
||||||
|
SstvImage::Pixel pixel;
|
||||||
|
if (!image.GetPixel(128, 91, pixel)) {
|
||||||
|
std::cout << "Failed to get pixel" << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::cout << (int)pixel.r << " " << (int)pixel.g << " " << (int)pixel.b
|
||||||
|
<< std::endl;
|
||||||
|
|
||||||
|
image.Write();
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
Plik binarny nie jest wyświetlany.
Po Szerokość: | Wysokość: | Rozmiar: 19 KiB |
Plik binarny nie jest wyświetlany.
Po Szerokość: | Wysokość: | Rozmiar: 17 KiB |
Plik binarny nie jest wyświetlany.
Po Szerokość: | Wysokość: | Rozmiar: 25 KiB |
|
@ -0,0 +1,195 @@
|
||||||
|
#include "sstv-image-tools.h"
|
||||||
|
|
||||||
|
#include <Magick++/Color.h>
|
||||||
|
#include <iostream>
|
||||||
|
#include <list>
|
||||||
|
|
||||||
|
SstvImage::Color::Color(int red, int green, int blue) {
|
||||||
|
this->r = red;
|
||||||
|
this->g = green;
|
||||||
|
this->b = blue;
|
||||||
|
}
|
||||||
|
|
||||||
|
SstvImage::SstvImage(SstvImage::Mode mode, std::string source_image_path,
|
||||||
|
std::string destination_image_path, bool crop)
|
||||||
|
: mode_(mode), crop_(crop) {
|
||||||
|
source_path_ = source_image_path;
|
||||||
|
if (destination_image_path == "") {
|
||||||
|
destination_path_ = source_image_path;
|
||||||
|
} else {
|
||||||
|
destination_path_ = destination_image_path;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
image_.read(source_image_path);
|
||||||
|
Scale();
|
||||||
|
} catch (Magick::Exception &error_) {
|
||||||
|
throw SstvImageToolsException("Failed to read image: " + source_image_path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void SstvImage::Write() {
|
||||||
|
try {
|
||||||
|
image_.write(destination_path_);
|
||||||
|
} catch (Magick::Exception &error_) {
|
||||||
|
throw SstvImageToolsException("Failed to write image: " +
|
||||||
|
destination_path_);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void SstvImage::AddCallSign(const std::string &callsign,
|
||||||
|
const SstvImage::Color &color) {
|
||||||
|
(void)color;
|
||||||
|
|
||||||
|
int font_size = 20 * height_scaler_;
|
||||||
|
|
||||||
|
Magick::DrawableFont font("Arial-Bold");
|
||||||
|
Magick::DrawablePointSize point_size(font_size);
|
||||||
|
Magick::DrawableText text(0, 0 + font_size, callsign);
|
||||||
|
Magick::DrawableStrokeColor stroke_color("red");
|
||||||
|
Magick::DrawableFillColor fill("green");
|
||||||
|
|
||||||
|
std::vector<Magick::Drawable> draw_list({font, point_size, text, stroke_color,
|
||||||
|
fill});
|
||||||
|
|
||||||
|
image_.draw(draw_list);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SstvImage::GetPixel(int x, int y, SstvImage::Pixel &pixel) {
|
||||||
|
if (x < 0 || x >= width_ || y < 0 || y >= height_) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
MagickCore::Quantum *pixels = image_.getPixels(0, 0, width_, height_);
|
||||||
|
unsigned index = (y * width_ + x) * image_.channels();
|
||||||
|
|
||||||
|
pixel.r = pixels[index];
|
||||||
|
pixel.g = pixels[index + 1];
|
||||||
|
pixel.b = pixels[index + 2];
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SstvImage::Scale() {
|
||||||
|
width_ = 0;
|
||||||
|
height_ = 0;
|
||||||
|
int scale_factor = 1;
|
||||||
|
switch (mode_) {
|
||||||
|
case SstvImage::Mode::ROBOT_8_BW:
|
||||||
|
case SstvImage::Mode::ROBOT_12_BW:
|
||||||
|
case SstvImage::Mode::ROBOT_12_COLOR:
|
||||||
|
width_ = 160;
|
||||||
|
height_ = 120;
|
||||||
|
scale_factor = 1.2;
|
||||||
|
break;
|
||||||
|
case SstvImage::Mode::ROBOT_24_BW:
|
||||||
|
case SstvImage::Mode::ROBOT_36_BW:
|
||||||
|
case SstvImage::Mode::ROBOT_24_COLOR:
|
||||||
|
case SstvImage::Mode::ROBOT_36_COLOR:
|
||||||
|
width_ = 320;
|
||||||
|
height_ = 240;
|
||||||
|
scale_factor = 1.0;
|
||||||
|
break;
|
||||||
|
case SstvImage::Mode::ROBOT_72_COLOR:
|
||||||
|
width_ = 640;
|
||||||
|
height_ = 480;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw SstvImageToolsException("Not yet implemented");
|
||||||
|
}
|
||||||
|
|
||||||
|
height_scaler_ = (height_ / 100.0) * scale_factor;
|
||||||
|
|
||||||
|
Magick::Geometry crop_size(width_, height_);
|
||||||
|
if (crop_) {
|
||||||
|
crop_size.fillArea(true);
|
||||||
|
image_.resize(crop_size);
|
||||||
|
image_.extent(crop_size, Magick::CenterGravity);
|
||||||
|
|
||||||
|
|
||||||
|
} else {
|
||||||
|
crop_size.aspect(true);
|
||||||
|
image_.resize(crop_size);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
bool ConvertToRobot8(std::string image_path) {
|
||||||
|
Magick::Image image;
|
||||||
|
Magick::Geometry crop_size(160, 120);
|
||||||
|
crop_size.aspect(true);
|
||||||
|
try {
|
||||||
|
image.read(image_path);
|
||||||
|
image.scale(crop_size);
|
||||||
|
image.quantizeColorSpace(Magick::GRAYColorspace);
|
||||||
|
image.quantizeColors(8);
|
||||||
|
image.quantize();
|
||||||
|
image.write(image_path + ".r8.png");
|
||||||
|
} catch (Magick::Exception &error_) {
|
||||||
|
std::cout << "Caught exception: " << error_.what() << std::endl;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ConvertToRobot36(std::string image_path) {
|
||||||
|
Magick::Image image;
|
||||||
|
Magick::Geometry crop_size(320, 240);
|
||||||
|
crop_size.aspect(true);
|
||||||
|
try {
|
||||||
|
image.read(image_path);
|
||||||
|
image.scale(crop_size);
|
||||||
|
image.quantizeColorSpace(Magick::YUVColorspace);
|
||||||
|
image.quantize();
|
||||||
|
image.write(image_path + ".r36.png");
|
||||||
|
} catch (Magick::Exception &error_) {
|
||||||
|
std::cout << "Caught exception: " << error_.what() << std::endl;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ConvertToCustom8(std::string image_path) {
|
||||||
|
Magick::Image image;
|
||||||
|
try {
|
||||||
|
image.read(image_path);
|
||||||
|
Magick::Geometry crop_size(400, 400);
|
||||||
|
crop_size.aspect(true);
|
||||||
|
image.scale(crop_size);
|
||||||
|
image.crop(crop_size);
|
||||||
|
image.quantizeColorSpace(Magick::GRAYColorspace);
|
||||||
|
image.quantizeColors(200);
|
||||||
|
image.quantize();
|
||||||
|
image.write(image_path + ".c8.png");
|
||||||
|
} catch (Magick::Exception &error_) {
|
||||||
|
std::cout << "exception: " << error_.what() << std::endl;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Pixels(std::string image_path) {
|
||||||
|
Magick::Image image;
|
||||||
|
try {
|
||||||
|
image.read(image_path);
|
||||||
|
Magick::Pixels view(image);
|
||||||
|
} catch (Magick::Exception &error_) {
|
||||||
|
std::cout << "exception: " << error_.what() << std::endl;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
|
@ -0,0 +1,92 @@
|
||||||
|
#ifndef IMAGE_TOOLS_H_
|
||||||
|
#define IMAGE_TOOLS_H_
|
||||||
|
|
||||||
|
#include <Magick++.h>
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
#include <string>
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
|
class SstvImage {
|
||||||
|
public:
|
||||||
|
enum class Mode {
|
||||||
|
ROBOT_8_BW, // 160x120 (4:3) Black and White
|
||||||
|
ROBOT_12_BW, // 160x120 (4:3) Black and White
|
||||||
|
ROBOT_24_BW, // 320x240 (4:3) Black and White
|
||||||
|
ROBOT_36_BW, // 320x240 (4:3) Black and White
|
||||||
|
ROBOT_12_COLOR, // 160x120 (4:3) Color
|
||||||
|
ROBOT_24_COLOR, // 320x240 (4:3) Color
|
||||||
|
ROBOT_36_COLOR, // 320x240 (4:3) Color
|
||||||
|
ROBOT_72_COLOR, // 640x480 (4:3) Color
|
||||||
|
CUSTOM_TEST };
|
||||||
|
|
||||||
|
struct Color {
|
||||||
|
int r = -1;
|
||||||
|
int g = -1;
|
||||||
|
int b = -1;
|
||||||
|
float gray_scale_value = -1;
|
||||||
|
Color(int r, int g, int b); // 0 - 255
|
||||||
|
Color(int gray_scale_value); // 0.0 - 1.0
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Pixel {
|
||||||
|
uint8_t r = -1;
|
||||||
|
uint8_t g = -1;
|
||||||
|
uint8_t b = -1;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Construct a new SSTV Image Tools object
|
||||||
|
*
|
||||||
|
* @param source_image_path The path to the image to be converted
|
||||||
|
* @param destination_image_path *Optional* If not specified, the source image
|
||||||
|
* will be overwritten
|
||||||
|
* @exception SstvImageToolsException If the image cannot be read
|
||||||
|
*/
|
||||||
|
SstvImage(SstvImage::Mode mode, std::string source_image_path,
|
||||||
|
std::string destination_image_path = "", bool crop = true);
|
||||||
|
~SstvImage() {}
|
||||||
|
|
||||||
|
void Write();
|
||||||
|
|
||||||
|
void AddCallSign(const std::string &callsign,
|
||||||
|
const SstvImage::Color &color = {-2, -2, -2});
|
||||||
|
void AddMessage(std::string message);
|
||||||
|
|
||||||
|
bool GetPixel(int x, int y, SstvImage::Pixel &pixel);
|
||||||
|
int GetWidth() { return width_; }
|
||||||
|
int GetHeight() { return height_; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
void Scale();
|
||||||
|
|
||||||
|
std::pair<int, int> WorkspaceToImageCoordinates(int x, int y);
|
||||||
|
|
||||||
|
int width_ = 0;
|
||||||
|
int height_ = 0;
|
||||||
|
double height_scaler_ = 1;
|
||||||
|
|
||||||
|
std::string source_path_ = "";
|
||||||
|
std::string destination_path_ = "";
|
||||||
|
Magick::Image image_ = Magick::Image();
|
||||||
|
SstvImage::Mode mode_;
|
||||||
|
bool crop_;
|
||||||
|
};
|
||||||
|
|
||||||
|
class SstvImageToolsException : public std::exception {
|
||||||
|
public:
|
||||||
|
SstvImageToolsException(std::string message) : message_(message) {}
|
||||||
|
~SstvImageToolsException() throw() {}
|
||||||
|
const char *what() const throw() { return message_.c_str(); }
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::string message_;
|
||||||
|
};
|
||||||
|
|
||||||
|
bool ConvertToRobot8(std::string image_path);
|
||||||
|
bool ConvertToRobot36(std::string image_path);
|
||||||
|
bool ConvertToCustom8(std::string image_path);
|
||||||
|
|
||||||
|
bool Pixels(std::string image_path);
|
||||||
|
|
||||||
|
#endif
|
Ładowanie…
Reference in New Issue