Skip to content

Commit

Permalink
Merge branch 'release/0.2'
Browse files Browse the repository at this point in the history
  • Loading branch information
tessarin committed Oct 5, 2017
2 parents cb3f8a1 + 515dfda commit 0041db8
Show file tree
Hide file tree
Showing 12 changed files with 232 additions and 34 deletions.
1 change: 0 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
dither
obj/
imgs/
*.plist
*.1
3 changes: 2 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
CFLAGS = -Wall -Isrc/include
LDLIBS = -lpng

SRCDIR = src
OBJDIR = obj
Expand All @@ -9,7 +10,7 @@ OBJECTS := $(SOURCES:$(SRCDIR)/%.c=$(OBJDIR)/%.o)
all: dither doc

dither: $(OBJECTS)
$(CC) -o $@ $^
$(CC) $^ $(LDLIBS) -o $@

analyse:
clang $(CFLAGS) --analyze $(SOURCES)
Expand Down
46 changes: 36 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,28 +1,28 @@
# Dither - Color Quantization and Dithering

This is a small program dedicated to reducing the number of colors in an
image. It operates with files in binary PPM format ([Portable PixMap][ppm]),
and features automatic palette generation (using a median-cut algorithm) and
dithering implemented with the Floyd-Steinberg method.
image. It operates with files in PNG or binary PPM format ([Portable
PixMap][ppm]), and features automatic palette generation (using a median-cut
algorithm) and dithering implemented with the Floyd-Steinberg method.

## Installing

#### On macOS with [Homebrew][brew]
#### On macOS with [Homebrew][brw]

$ brew install tessarin/core/dither

#### Manual Installation

Running `make` will compile the program and generate the documentation
(requires Perl). To install, move the executable and manual file to the
appropriate directories.
First, install [`libpng`][lpg]. Running `make` will then compile the program
and generate the documentation (requires Perl). To install, move the
executable and manual files to appropriate directories in your system.

$ make
$ mv dither ~/bin
$ mv dither.1 ~/man/man1

Individual targets can also be specified for only compiling or generating the
manual page:
Individual targets can also be specified to only compile the program or to
generate the manual page:

$ make dither
$ make doc
Expand All @@ -47,6 +47,32 @@ processing an image. Other possible palettes include:
Dithering on the final image can be disabled and the program can also be used
just to generate a palette.

## Samples

#### Automatic Palette

$ dither -p auto.32 bird-original.png bird-auto.32.png

![original bird](samples/bird-original.png)
![generated bird](samples/bird-auto.32.png)

#### 1-bit Black & White

$ dither -p bw flower-original.png flower-bw.png

![original flower](samples/flower-original.png)
![generated flower](samples/flower-bw.png)

#### 50% Gray Test

Generates a perfect checkerboard pattern.

$ dither -p bw gray-original.png gray-dithered.png

![original gray](samples/gray-original.png)
![dithered gray](samples/gray-dithered.png)

[ppm]: https://en.wikipedia.org/wiki/Netpbm_format
[brew]: https://brew.sh
[brw]: https://brew.sh
[lpg]: http://www.libpng.org/pub/png/libpng.html
[man]: dither.pod
13 changes: 9 additions & 4 deletions dither.pod
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,22 @@ B<dither> [I<-dv>] [I<-p name>[.I<size>]] I<input> I<output>

=head1 DESCRIPTION

B<dither> processes image files in binary PPM format with the goal of reducing
its number of colors. Functionality includes generating a palette of colors
automatically and writing the output image with the signal optionally dithered
(Floyd-Steinberg method).
B<dither> processes image files in PNG or binary PPM format with the goal of
reducing its number of colors. Functionality includes generating a palette of
colors automatically and writing the output image with the signal optionally
dithered (Floyd-Steinberg method).

Arguments for the input and output file are both required.

The palette used in the operation can be generated from different presets and
sizes, or be completely customized. Details about palette generation are
described below in the corresponding section.

The input file format is deduced by analysing the image header. The output
file format is decided according to its extension. Use a C<.png> extension to
have output written as a PNG file. Any other extension will result in a binary
PPM file.

=head2 Options

Accepted options are:
Expand Down
Binary file added samples/bird-auto.32.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added samples/bird-original.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added samples/flower-bw.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added samples/flower-original.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added samples/gray-dithered.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added samples/gray-original.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
193 changes: 175 additions & 18 deletions src/DTImage.c
Original file line number Diff line number Diff line change
Expand Up @@ -8,31 +8,46 @@

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <DTImage.h>
#include <png.h>

void ReadPixelsFromFile(DTImage *img, FILE *file);
#define HEADER_LEN 8
#define PPM_HEADER 2

int ReadDataFromFile(DTImage *img, FILE *file);
DTImageType IdentifyImageType(char *header);
png_bytep *PNGRowPointersForImage(DTImage *);

DTImage *
CreateImageFromFile(char *filename)
{
FILE *file = fopen(filename, "r");
FILE *file = fopen(filename, "rb");
if (file == NULL) {
perror("Could not open image file");
return NULL;
}

char identifier[2];
size_t read = fread(&identifier, 1, 2, file);
if (read != 2 || identifier[0] != 'P' || identifier[1] != '6') {
char header[HEADER_LEN];
size_t read = fread(&header, 1, HEADER_LEN, file);
if (read != HEADER_LEN) {
fprintf(stderr, "Failed to read image header.\n");
return NULL;
}

DTImageType type = IdentifyImageType(header);
if (type == t_UNKNOWN) {
fprintf(stderr, "Image file of unrecognized format.\n");
return NULL;
}

/* initialize image and read pixels */
DTImage *image = malloc(sizeof(DTImage));

fscanf(file, " %d %d %*d ", &image->width, &image->height);
image->resolution = image->width * image->height;
ReadPixelsFromFile(image, file);
image->type = type;
if (ReadDataFromFile(image, file)) {
fprintf(stderr, "Failed to read image content.\n");
return NULL;
}

fclose(file);

Expand All @@ -42,15 +57,61 @@ CreateImageFromFile(char *filename)
void
WriteImageToFile(DTImage *img, char *filename)
{
FILE *file = fopen(filename, "w");
FILE *file = fopen(filename, "wb");
if (file == NULL) {
perror("Could not open output file");
return;
}

fprintf(file, "P6\n%d %d\n255\n", img->width, img->height);
for (int i = 0; i < img->resolution; i++)
fwrite(&img->pixels[i], sizeof(DTPixel), 1, file);
/* judge desired output format by file extension */
size_t fn_l = strlen(filename);
size_t png_l = strlen(e_PNG);

if (fn_l > png_l && !strcmp(filename+fn_l-png_l, e_PNG)) {
/* PNG */

/* create data and info structs */
png_structp png = png_create_write_struct(
PNG_LIBPNG_VER_STRING, NULL, NULL, NULL
);
if (!png) return;

png_infop info = png_create_info_struct(png);
if (!info) return;

if(setjmp(png_jmpbuf(png))) return;

png_init_io(png, file);

/* output in 8-bit RGB */
png_set_IHDR(
png,
info,
img->width, img->height,
8,
PNG_COLOR_TYPE_RGB,
PNG_INTERLACE_NONE,
PNG_COMPRESSION_TYPE_DEFAULT,
PNG_FILTER_TYPE_DEFAULT
);
png_write_info(png, info);

png_bytep *rowPointers = PNGRowPointersForImage(img);
png_write_image(png, rowPointers);

/* finish writing and cleanup memory */
png_write_end(png, NULL);
png_destroy_write_struct(&png, &info);
free(rowPointers);

} else {
/* PPM */
fprintf(file, "P6\n%d %d\n255\n", img->width, img->height);
for (int i = 0; i < img->resolution; i++)
fwrite(&img->pixels[i], sizeof(DTPixel), 1, file);
}

fclose(file);
}

DTPixel
Expand All @@ -64,11 +125,107 @@ PixelFromRGB(byte r, byte g, byte b)
return pixel;
}

void
ReadPixelsFromFile(DTImage *img, FILE *file)
/* returns non-zero if error occurred reading the files */
int
ReadDataFromFile(DTImage *img, FILE *file)
{
if (img->type == t_PPM) {
/* simple format, done directly */
fseek(file, PPM_HEADER, SEEK_SET);
fscanf(file, " %d %d %*d ", &img->width, &img->height);
img->pixels = malloc(sizeof(DTPixel) * img->width * img->height);
img->resolution = img->width * img->height;

for (int i = 0; i < img->resolution; i++)
fread(&img->pixels[i], sizeof(DTPixel), 1, file);
}
if (img->type == t_PNG) {
/* create data and info structs */
png_structp png = png_create_read_struct(
PNG_LIBPNG_VER_STRING, NULL, NULL, NULL
);
if (!png) return 1;

png_infop info = png_create_info_struct(png);
if (!info) return 2;

/* in case an error occurs */
if(setjmp(png_jmpbuf(png))) return 3;

png_init_io(png, file);
png_set_sig_bytes(png, HEADER_LEN);
png_read_info(png, info);

png_byte color_type, bit_depth;

img->width = png_get_image_width(png, info);
img->height = png_get_image_height(png, info);
color_type = png_get_color_type(png, info);
bit_depth = png_get_bit_depth(png, info);

img->resolution = img->width * img->height;

/* transform different types into 8bit RGB */
if (bit_depth == 16) png_set_strip_16(png);

if (color_type == PNG_COLOR_TYPE_PALETTE)
png_set_palette_to_rgb(png);

if (color_type == PNG_COLOR_TYPE_GRAY && bit_depth < 8)
png_set_expand_gray_1_2_4_to_8(png);

if (png_get_valid(png, info, PNG_INFO_tRNS))
png_set_tRNS_to_alpha(png);

if (color_type & PNG_COLOR_MASK_ALPHA)
png_set_strip_alpha(png);

if (color_type == PNG_COLOR_TYPE_GRAY ||
color_type == PNG_COLOR_TYPE_GRAY_ALPHA)
png_set_gray_to_rgb(png);

png_read_update_info(png, info);

img->pixels = malloc(sizeof(DTPixel) * img->width * img->height);

/* check if data is realy 8-bit RGB */
if (png_get_rowbytes(png, info) != img->width * sizeof(DTPixel))
return 4;

png_bytep *rowPointers = PNGRowPointersForImage(img);
png_read_image(png, rowPointers);

/* finish reading and cleanup memory */
png_read_end(png, info);
png_destroy_read_struct(&png, &info, NULL);
free(rowPointers);
}

return 0;
}

DTImageType
IdentifyImageType(char *header)
{
img->pixels = malloc(sizeof(DTPixel) * img->width * img->height);
if (!png_sig_cmp((png_const_bytep)header, 0, HEADER_LEN))
return t_PNG;

if (header[0] == 'P' && header[1] == '6')
return t_PPM;

return t_UNKNOWN;
}

/* setup array of pointers used by libpng to
* point to our own allocated memory */
png_bytep *
PNGRowPointersForImage(DTImage *img)
{
png_bytep *rowPointers = malloc(sizeof(png_bytep) * img->height);
png_size_t rowSize = img->width * sizeof(DTPixel);

for (int i = 0; i < img->height; i++)
rowPointers[i] = (png_bytep)img->pixels + rowSize * i;

for (int i = 0; i < img->resolution; i++)
fread(&img->pixels[i], sizeof(DTPixel), 1, file);
return rowPointers;
}
10 changes: 10 additions & 0 deletions src/include/DTImage.h
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,25 @@
#ifndef DT_IMAGE
#define DT_IMAGE

#define e_PPM ".ppm"
#define e_PNG ".png"

typedef unsigned char byte;

typedef struct {
byte r, g, b;
} DTPixel;

typedef enum {
t_PPM,
t_PNG,
t_UNKNOWN
} DTImageType;

typedef struct {
int width;
int height;
DTImageType type;
unsigned long resolution;
DTPixel *pixels;
} DTImage;
Expand Down

0 comments on commit 0041db8

Please sign in to comment.