added resizer

This commit is contained in:
albert
2010-02-08 01:40:39 -05:00
parent 3d70335d92
commit 9c441aff4c
24 changed files with 1715 additions and 341 deletions

View File

@@ -0,0 +1,58 @@
#include <string.h>
#include <gd.h>
#include "GIFReader.h"
#include "RowBuffer.h"
#include "Resize.h"
bool GIF::Read(FILE *f, Resizer *resizer, char error[1024])
{
RowBuffer Rows;
bool Ret = false;
gdImage *image = gdImageCreateFromGif(f);
if(!image)
{
strcpy(error, "couldn't read GIF");
return false;
}
if(!Rows.Init(image->sx, image->sy, 3))
{
strcpy(error, "out of memory");
goto cleanup;
}
resizer->SetSource(image->sx, image->sy, 3);
for(int y = 0; y < image->sy; ++y)
{
uint8_t *p = Rows.GetRow(y);
if(p == NULL)
{
strcpy(error, "out of memory");
goto cleanup;
}
for(int x = 0; x < image->sx; ++x)
{
int c = gdImageGetTrueColorPixel(image, x, y);
(*p++) = gdTrueColorGetRed(c);
(*p++) = gdTrueColorGetGreen(c);
(*p++) = gdTrueColorGetBlue(c);
}
int DiscardRow;
if(!resizer->Run(Rows.GetRows(), Rows.GetStartRow(), Rows.GetEndRow(), DiscardRow))
{
strcpy(error, resizer->GetError());
goto cleanup;
}
Rows.DiscardRows(DiscardRow);
}
Ret = true;
cleanup:
gdImageDestroy(image);
return Ret;
}

View File

@@ -0,0 +1,11 @@
#ifndef GIF_READER_H
#define GIF_READER_H
#include "Reader.h"
class GIF: public Reader
{
public:
bool Read(FILE *f, Resizer *resizer, char error[1024]);
};
#endif

View File

@@ -0,0 +1,155 @@
#include <string.h>
#include "JPEGReader.h"
#include "RowBuffer.h"
#include "Resize.h"
#include <algorithm>
using namespace std;
static void jpeg_error_exit(j_common_ptr CInfo)
{
jpeg_error *myerr = (jpeg_error *) CInfo->err;
(*CInfo->err->format_message) (CInfo, myerr->buffer);
longjmp(myerr->setjmp_buffer, 1);
}
static void jpeg_warning(j_common_ptr cinfo, int msg_level)
{
}
JPEGCompressor::JPEGCompressor(FILE *f)
{
m_File = f;
memset(&m_CInfo, 0, sizeof(m_CInfo));
}
JPEGCompressor::~JPEGCompressor()
{
jpeg_destroy_compress(&m_CInfo);
}
const char *JPEGCompressor::GetError() const
{
return m_JErr.buffer;
}
bool JPEGCompressor::Init(int width, int height, int quality)
{
m_CInfo.err = jpeg_std_error(&m_JErr.pub);
m_JErr.pub.error_exit = jpeg_error_exit;
m_JErr.pub.emit_message = jpeg_warning;
if(setjmp(m_JErr.setjmp_buffer))
return false;
jpeg_create_compress(&m_CInfo);
jpeg_stdio_dest(&m_CInfo, m_File);
m_CInfo.image_width = width;
m_CInfo.image_height = height;
m_CInfo.input_components = 3; /* # of color components per pixel */
m_CInfo.in_color_space = JCS_RGB; /* colorspace of input image */
jpeg_set_defaults(&m_CInfo);
jpeg_simple_progression(&m_CInfo);
jpeg_set_quality(&m_CInfo, quality, TRUE); // limit to baseline-JPEG values
jpeg_start_compress(&m_CInfo, TRUE);
return true;
}
int JPEGCompressor::GetWidth() const
{
return m_CInfo.image_width;
}
int JPEGCompressor::GetHeight() const
{
return m_CInfo.image_height;
}
bool JPEGCompressor::WriteRow(uint8_t *row)
{
if(setjmp(m_JErr.setjmp_buffer))
return false;
jpeg_write_scanlines(&m_CInfo, (JSAMPLE **) &row, 1);
return true;
}
bool JPEGCompressor::Finish()
{
if(setjmp(m_JErr.setjmp_buffer))
return false;
jpeg_finish_compress(&m_CInfo);
return true;
}
bool JPEG::Read(FILE *f, Resizer *resizer, char error[1024])
{
// JMSG_LENGTH_MAX <= sizeof(error)
m_JErr.buffer = error;
RowBuffer Rows;
m_Resizer = resizer;
struct jpeg_decompress_struct CInfo;
CInfo.err = jpeg_std_error(&m_JErr.pub);
m_JErr.pub.error_exit = jpeg_error_exit;
m_JErr.pub.emit_message = jpeg_warning;
bool Ret = false;
if(setjmp(m_JErr.setjmp_buffer))
goto cleanup;
jpeg_create_decompress(&CInfo);
jpeg_stdio_src(&CInfo, f);
jpeg_read_header(&CInfo, TRUE);
CInfo.out_color_space = JCS_RGB;
jpeg_start_decompress(&CInfo);
if(!Rows.Init(CInfo.output_width, CInfo.output_height, 3))
{
strcpy(error, "out of memory");
goto cleanup;
}
m_Resizer->SetSource(CInfo.output_width, CInfo.output_height, 3);
while(CInfo.output_scanline < CInfo.output_height)
{
uint8_t *p = Rows.GetRow(CInfo.output_scanline);
if(p == NULL)
{
strcpy(error, "out of memory");
goto cleanup;
}
jpeg_read_scanlines(&CInfo, &p, 1);
int DiscardRow;
if(!m_Resizer->Run(Rows.GetRows(), Rows.GetStartRow(), min(Rows.GetEndRow(), (int) CInfo.output_scanline+1), DiscardRow))
{
strcpy(error, m_Resizer->GetError());
goto cleanup;
}
Rows.DiscardRows(DiscardRow);
}
jpeg_finish_decompress(&CInfo);
Ret = true;
cleanup:
jpeg_destroy_decompress(&CInfo);
return Ret;
}

View File

@@ -0,0 +1,47 @@
#ifndef JPEG_READER_H
#define JPEG_READER_H
#include <stdio.h>
#include <stdint.h>
#include <setjmp.h>
#include "jpeglib-extern.h"
#include "Reader.h"
struct jpeg_error
{
struct jpeg_error_mgr pub;
jmp_buf setjmp_buffer;
char *buffer;
};
class JPEG: public Reader
{
public:
bool Read(FILE *f, Resizer *resizer, char error[1024]);
private:
Resizer *m_Resizer;
struct jpeg_error m_JErr;
};
class JPEGCompressor
{
public:
JPEGCompressor(FILE *f);
~JPEGCompressor();
bool Init(int width, int height, int quality);
bool WriteRow(uint8_t *row);
bool Finish();
int GetWidth() const;
int GetHeight() const;
const char *GetError() const;
private:
FILE *m_File;
struct jpeg_compress_struct m_CInfo;
struct jpeg_error m_JErr;
};
#endif

View File

@@ -0,0 +1,157 @@
SHELL = /bin/sh
#### Start of system configuration section. ####
srcdir = .
topdir = /opt/local/lib/ruby/1.8/i686-darwin10
hdrdir = $(topdir)
VPATH = $(srcdir):$(topdir):$(hdrdir)
exec_prefix = $(prefix)
prefix = $(DESTDIR)/opt/local
sharedstatedir = $(prefix)/com
mandir = $(DESTDIR)/opt/local/share/man
psdir = $(docdir)
oldincludedir = $(DESTDIR)/usr/include
localedir = $(datarootdir)/locale
bindir = $(exec_prefix)/bin
libexecdir = $(exec_prefix)/libexec
sitedir = $(libdir)/ruby/site_ruby
htmldir = $(docdir)
vendorarchdir = $(vendorlibdir)/$(sitearch)
includedir = $(prefix)/include
infodir = $(datarootdir)/info
vendorlibdir = $(vendordir)/$(ruby_version)
sysconfdir = $(prefix)/etc
libdir = $(exec_prefix)/lib
sbindir = $(exec_prefix)/sbin
rubylibdir = $(libdir)/ruby/$(ruby_version)
docdir = $(datarootdir)/doc/$(PACKAGE)
dvidir = $(docdir)
vendordir = $(DESTDIR)/opt/local/lib/ruby/vendor_ruby
datarootdir = $(prefix)/share
pdfdir = $(docdir)
archdir = $(rubylibdir)/$(arch)
sitearchdir = $(sitelibdir)/$(sitearch)
datadir = $(datarootdir)
localstatedir = $(prefix)/var
sitelibdir = $(sitedir)/$(ruby_version)
CC = g++
LIBRUBY = $(LIBRUBY_SO)
LIBRUBY_A = lib$(RUBY_SO_NAME)-static.a
LIBRUBYARG_SHARED = -l$(RUBY_SO_NAME)
LIBRUBYARG_STATIC = -l$(RUBY_SO_NAME)-static
RUBY_EXTCONF_H =
CFLAGS = -fno-common -O2 -fno-exceptions -Wall -arch x86_64
INCFLAGS = -I. -I. -I/opt/local/lib/ruby/1.8/i686-darwin10 -I.
DEFS =
CPPFLAGS = -DHAVE_GD_H -DHAVE_GDIMAGECREATEFROMGIF -DHAVE_GDIMAGEJPEG -DHAVE_JPEG_SET_QUALITY -DHAVE_PNG_SET_EXPAND_GRAY_1_2_4_TO_8 -I/opt/local/include -D_XOPEN_SOURCE -D_DARWIN_C_SOURCE -I/opt/local/include
CXXFLAGS = $(CFLAGS)
ldflags = -L. -L/opt/local/lib
dldflags =
archflag = -arch x86_64
DLDFLAGS = $(ldflags) $(dldflags) $(archflag)
LDSHARED = $(CC) -dynamic -bundle -undefined suppress -flat_namespace
AR = ar
EXEEXT =
RUBY_INSTALL_NAME = ruby
RUBY_SO_NAME = ruby
arch = i686-darwin10
sitearch = i686-darwin10
ruby_version = 1.8
ruby = /opt/local/bin/ruby
RUBY = $(ruby)
RM = rm -f
MAKEDIRS = mkdir -p
INSTALL = /usr/bin/install -c
INSTALL_PROG = $(INSTALL) -m 0755
INSTALL_DATA = $(INSTALL) -m 644
COPY = cp
#### End of system configuration section. ####
preload =
libpath = . $(libdir)
LIBPATH = -L. -L$(libdir)
DEFFILE =
CLEANFILES = mkmf.log
DISTCLEANFILES =
extout =
extout_prefix =
target_prefix =
LOCAL_LIBS =
LIBS = $(LIBRUBYARG_SHARED) -lpng -ljpeg -lgd -lpthread -ldl -lobjc
SRCS = danbooru_image_resizer.cpp GIFReader.cpp JPEGReader.cpp PNGReader.cpp Resize.cpp RowBuffer.cpp
OBJS = danbooru_image_resizer.o GIFReader.o JPEGReader.o PNGReader.o Resize.o RowBuffer.o
TARGET = danbooru_image_resizer
DLLIB = $(TARGET).bundle
EXTSTATIC =
STATIC_LIB =
BINDIR = $(bindir)
RUBYCOMMONDIR = $(sitedir)$(target_prefix)
RUBYLIBDIR = $(sitelibdir)$(target_prefix)
RUBYARCHDIR = $(sitearchdir)$(target_prefix)
TARGET_SO = $(DLLIB)
CLEANLIBS = $(TARGET).bundle $(TARGET).il? $(TARGET).tds $(TARGET).map
CLEANOBJS = *.o *.a *.s[ol] *.pdb *.exp *.bak
all: $(DLLIB)
static: $(STATIC_LIB)
clean:
@-$(RM) $(CLEANLIBS) $(CLEANOBJS) $(CLEANFILES)
distclean: clean
@-$(RM) Makefile $(RUBY_EXTCONF_H) conftest.* mkmf.log
@-$(RM) core ruby$(EXEEXT) *~ $(DISTCLEANFILES)
realclean: distclean
install: install-so install-rb
install-so: $(RUBYARCHDIR)
install-so: $(RUBYARCHDIR)/$(DLLIB)
$(RUBYARCHDIR)/$(DLLIB): $(DLLIB)
$(INSTALL_PROG) $(DLLIB) $(RUBYARCHDIR)
install-rb: pre-install-rb install-rb-default
install-rb-default: pre-install-rb-default
pre-install-rb: Makefile
pre-install-rb-default: Makefile
$(RUBYARCHDIR):
$(MAKEDIRS) $@
site-install: site-install-so site-install-rb
site-install-so: install-so
site-install-rb: install-rb
.SUFFIXES: .c .m .cc .cxx .cpp .C .o
.cc.o:
$(CXX) $(INCFLAGS) $(CPPFLAGS) $(CXXFLAGS) -c $<
.cxx.o:
$(CXX) $(INCFLAGS) $(CPPFLAGS) $(CXXFLAGS) -c $<
.cpp.o:
$(CXX) $(INCFLAGS) $(CPPFLAGS) $(CXXFLAGS) -c $<
.C.o:
$(CXX) $(INCFLAGS) $(CPPFLAGS) $(CXXFLAGS) -c $<
.c.o:
$(CC) $(INCFLAGS) $(CPPFLAGS) $(CFLAGS) -c $<
$(DLLIB): $(OBJS) Makefile
@-$(RM) $@
$(LDSHARED) -o $@ $(OBJS) $(LIBPATH) $(DLDFLAGS) $(LOCAL_LIBS) $(LIBS)
$(OBJS): ruby.h defines.h

View File

@@ -0,0 +1,138 @@
#include <errno.h>
#include <stdio.h>
#include "PNGReader.h"
#include "Resize.h"
#include <algorithm>
using namespace std;
void PNG::Error(png_struct *png, const char *error)
{
png_error_info *info = (png_error_info *) png->error_ptr;
strncpy(info->err, error, 1024);
info->err[1023] = 0;
longjmp(png->jmpbuf, 1);
}
void PNG::Warning(png_struct *png, const char *warning)
{
}
void PNG::InfoCallback(png_struct *png, png_info *info_ptr)
{
PNG *data = (PNG *) png_get_progressive_ptr(png);
png_uint_32 width, height;
int bit_depth, color_type;
png_get_IHDR(png, info_ptr, &width, &height, &bit_depth, &color_type, NULL, NULL, NULL);
png_set_palette_to_rgb(png);
png_set_tRNS_to_alpha(png);
png_set_filler(png, 0xFF, PNG_FILLER_AFTER);
if(bit_depth < 8)
png_set_packing(png);
if(color_type == PNG_COLOR_TYPE_GRAY && bit_depth < 8)
png_set_expand_gray_1_2_4_to_8(png);
if(bit_depth == 16)
png_set_strip_16(png);
data->m_Passes = png_set_interlace_handling(png);
if (color_type == PNG_COLOR_TYPE_GRAY || color_type == PNG_COLOR_TYPE_GRAY_ALPHA)
png_set_gray_to_rgb(png);
if(!data->m_Rows.Init(width, height, 4))
Error(png, "out of memory");
png_read_update_info(png, info_ptr);
data->m_Resizer->SetSource(width, height, 4);
}
void PNG::RowCallback(png_struct *png, png_byte *new_row, png_uint_32 row_num, int pass)
{
PNG *data = (PNG *) png_get_progressive_ptr(png);
uint8_t *p = data->m_Rows.GetRow(row_num);
if(p == NULL)
Error(png, "out of memory");
png_progressive_combine_row(png, p, new_row);
if(pass != data->m_Passes - 1)
return;
/* We've allocated data->m_RowsAllocated, but if we're doing multiple passes, only
* rows 0 to row_num will actually have usable data. */
int DiscardRow;
int LastRow = min(data->m_Rows.GetEndRow(), (int) row_num+1);
if(!data->m_Resizer->Run(data->m_Rows.GetRows(), data->m_Rows.GetStartRow(), LastRow, DiscardRow))
Error(png, data->m_Resizer->GetError());
/* If we're interlaced, never discard rows. */
if(data->m_Passes == 1)
data->m_Rows.DiscardRows(DiscardRow);
}
void PNG::EndCallback(png_struct *png, png_info *info)
{
PNG *data = (PNG *) png_get_progressive_ptr(png);
data->m_Done = true;
}
bool PNG::Read(FILE *f, Resizer *resizer, char error[1024])
{
m_Resizer = resizer;
png_error_info err;
err.err = error;
png_struct *png = png_create_read_struct(PNG_LIBPNG_VER_STRING, &err, Error, Warning);
if(png == NULL)
{
sprintf(error, "creating png_create_read_struct failed");
return false;
}
png_info *info_ptr = png_create_info_struct(png);
if(info_ptr == NULL)
{
png_destroy_read_struct(&png, NULL, NULL);
sprintf(error, "creating png_create_info_struct failed");
return false;
}
if(setjmp(png->jmpbuf))
{
png_destroy_read_struct(&png, &info_ptr, NULL);
return false;
}
png_set_progressive_read_fn(png, this, InfoCallback, RowCallback, EndCallback);
while(1)
{
png_byte buf[1024*16];
int ret = fread(buf, 1, sizeof(buf), f);
if(ret == 0)
break;
if(ferror(f))
{
strcpy(error, strerror(errno));
png_destroy_read_struct(&png, &info_ptr, NULL);
return false;
}
png_process_data(png, info_ptr, buf, ret);
}
if(!m_Done)
{
strcpy(error, "incomplete file");
png_destroy_read_struct(&png, &info_ptr, NULL);
return false;
}
png_destroy_read_struct(&png, &info_ptr, NULL);
return true;
}

View File

@@ -0,0 +1,37 @@
#ifndef PNG_READER_H
#define PNG_READER_H
#include <png.h>
#include "Reader.h"
#include "RowBuffer.h"
struct png_error_info
{
char *err;
};
class PNG: public Reader
{
public:
PNG()
{
m_Done = false;
}
bool Read(FILE *f, Resizer *resizer, char error[1024]);
private:
RowBuffer m_Rows;
Resizer *m_Resizer;
bool m_Done;
int m_Passes;
static void Error(png_struct *png, const char *error);
static void Warning(png_struct *png, const char *warning);
static void InfoCallback(png_struct *png, png_info *info_ptr);
static void RowCallback(png_struct *png, png_byte *new_row, png_uint_32 row_num, int pass);
static void EndCallback(png_struct *png, png_info *info);
};
#endif

View File

@@ -0,0 +1,14 @@
#ifndef READER_H
#define READER_H
#include <stdio.h>
class Resizer;
class Reader
{
public:
virtual ~Reader() { }
virtual bool Read(FILE *f, Resizer *rp, char errorbuf[1024]) = 0;
};
#endif

View File

@@ -0,0 +1,184 @@
#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include "Resize.h"
#include "JPEGReader.h"
#include <algorithm>
using namespace std;
Resizer::Resizer(JPEGCompressor *Compressor)
{
m_Compressor = Compressor;
m_CurrentY = 0;
m_OutBuf = NULL;
}
Resizer::~Resizer()
{
if(m_OutBuf)
free(m_OutBuf);
}
const char *Resizer::GetError() const
{
return m_Compressor->GetError();
}
void Resizer::SetSource(int Width, int Height, int BPP)
{
m_SourceWidth = Width;
m_SourceHeight = Height;
m_SourceBPP = BPP;
}
bool Resizer::SetDest(int Width, int Height, int Quality)
{
m_DestWidth = Width;
m_DestHeight = Height;
m_OutBuf = (uint8_t *) malloc(Width*3);
return m_Compressor->Init(Width, Height, Quality);
}
#define scale(x, l1, h1, l2, h2) (((x)-(l1))*((h2)-(l2))/((h1)-(l1))+(l2))
static void Average(const uint8_t *const *src, float Colors[3], float SourceXStart, float SourceXEnd, float SourceYStart, float SourceYEnd, int SourceBPP)
{
float Total = 0.0f;
for(float y = SourceYStart; y < SourceYEnd; ++y)
{
float YCoverage = 1.0f;
if(int(y) == int(SourceYStart))
YCoverage -= y - int(y);
if(int(y) == int(SourceYEnd))
YCoverage -= 1.0f - (SourceYEnd - int(SourceYEnd));
const uint8_t *xsrc=src[(int) y]+(int)SourceXStart*SourceBPP;
/* The two conditionals can only be true on the first and last iteration of the loop,
* so unfold those iterations and pull the conditionals out of the inner loop. */
/* while(x < SourceXEnd)
{
float XCoverage = 1.0f;
if(int(x) == int(SourceXStart))
XCoverage -= x - int(x);
if(int(x) == int(SourceXEnd))
XCoverage -= 1.0f - (SourceXEnd - int(SourceXEnd));
Colors[0] += xsrc[0] * XCoverage * YCoverage;
Colors[1] += xsrc[1] * XCoverage * YCoverage;
Colors[2] += xsrc[2] * XCoverage * YCoverage;
if(SourceBPP == 4)
Colors[3] += xsrc[3] * XCoverage * YCoverage;
xsrc += SourceBPP;
Total += XCoverage * YCoverage;
++x;
}
*/
float x = int(SourceXStart);
if(x < SourceXEnd)
{
float XCoverage = 1.0f;
if(int(x) == int(SourceXStart))
XCoverage -= x - int(x);
if(int(x) == int(SourceXEnd))
XCoverage -= 1.0f - (SourceXEnd - int(SourceXEnd));
Colors[0] += xsrc[0] * XCoverage * YCoverage;
Colors[1] += xsrc[1] * XCoverage * YCoverage;
Colors[2] += xsrc[2] * XCoverage * YCoverage;
if(SourceBPP == 4)
Colors[3] += xsrc[3] * XCoverage * YCoverage;
xsrc += SourceBPP;
Total += XCoverage * YCoverage;
++x;
}
while(x < SourceXEnd-1)
{
Colors[0] += xsrc[0] * YCoverage;
Colors[1] += xsrc[1] * YCoverage;
Colors[2] += xsrc[2] * YCoverage;
if(SourceBPP == 4)
Colors[3] += xsrc[3] * YCoverage;
xsrc += SourceBPP;
Total += YCoverage;
++x;
}
if(x < SourceXEnd)
{
float XCoverage = 1.0f;
if(int(x) == int(SourceXStart))
XCoverage -= x - int(x);
if(int(x) == int(SourceXEnd))
XCoverage -= 1.0f - (SourceXEnd - int(SourceXEnd));
Colors[0] += xsrc[0] * XCoverage * YCoverage;
Colors[1] += xsrc[1] * XCoverage * YCoverage;
Colors[2] += xsrc[2] * XCoverage * YCoverage;
if(SourceBPP == 4)
Colors[3] += xsrc[3] * XCoverage * YCoverage;
xsrc += SourceBPP;
Total += XCoverage * YCoverage;
}
}
if(Total != 0.0f)
for(int i = 0; i < 4; ++i)
Colors[i] /= Total;
}
bool Resizer::Run(const uint8_t *const *Source, int StartRow, int EndRow, int &DiscardRow)
{
while(m_CurrentY < m_DestHeight)
{
float SourceYStart = scale((float) m_CurrentY, 0.0f, (float) m_DestHeight, 0.0f, (float) m_SourceHeight);
float SourceYEnd = scale((float) m_CurrentY + 1, 0.0f, (float) m_DestHeight, 0.0f, (float) m_SourceHeight);
DiscardRow = int(SourceYStart)-1;
if(EndRow != m_SourceHeight && int(SourceYEnd)+1 > EndRow-1)
return true;
assert(SourceYStart>=StartRow);
uint8_t *Output = m_OutBuf;
for(int x = 0; x < m_DestWidth; ++x)
{
float SourceXStart = scale((float) x, 0.0f, (float) m_DestWidth, 0.0f, (float) m_SourceWidth);
float SourceXEnd = scale((float) x + 1, 0.0f, (float) m_DestWidth, 0.0f, (float) m_SourceWidth);
float Colors[4] = { 0.0 };
Average(Source, Colors, SourceXStart, SourceXEnd, SourceYStart, SourceYEnd, m_SourceBPP);
if(m_SourceBPP == 4)
{
for(int i = 0; i < 3; ++i)
Colors[i] *= Colors[3]/255.0f;
}
Output[0] = (uint8_t) min(255, int(Colors[0]));
Output[1] = (uint8_t) min(255, int(Colors[1]));
Output[2] = (uint8_t) min(255, int(Colors[2]));
Output += 3;
}
if(!m_Compressor->WriteRow((JSAMPLE *) m_OutBuf))
return false;
++m_CurrentY;
}
if(m_CurrentY == m_DestHeight)
{
if(!m_Compressor->Finish())
return false;
}
return true;
}

View File

@@ -0,0 +1,41 @@
#ifndef RESIZE_H
#define RESIZE_H
#include <stdint.h>
class JPEGCompressor;
class Resizer
{
public:
Resizer(JPEGCompressor *Compressor);
~Resizer();
// BPP is 3 or 4, indicating RGB or RGBA.
void SetSource(int Width, int Height, int BPP);
bool SetDest(int Width, int Height, int Quality);
/*
* Resize part of an image.
*
* [FirstRow,LastRow) is a range indicating which elements in src[] are available.
* On return, any rows in [0,DiscardRow) are no longer needed and can be deleted.
*/
bool Run(const uint8_t *const *src, int FirstRow, int LastRow, int &DiscardRow);
const char *GetError() const;
private:
JPEGCompressor *m_Compressor;
uint8_t *m_OutBuf;
int m_SourceWidth;
int m_SourceHeight;
int m_SourceBPP;
int m_DestWidth;
int m_DestHeight;
float m_CurrentY;
};
#endif

View File

@@ -0,0 +1,81 @@
#include <stdio.h>
#include <string.h>
#include <assert.h>
#include "RowBuffer.h"
#include <algorithm>
using namespace std;
RowBuffer::RowBuffer()
{
m_Rows = NULL;
m_StartRow = 0;
m_EndRow = 0;
m_BPP = 0;
m_Height = 0;
}
RowBuffer::~RowBuffer()
{
for(int i = 0; i < m_Height; ++i)
delete [] m_Rows[i];
delete [] m_Rows;
}
bool RowBuffer::Init(int Width, int Height, int BPP)
{
m_Width = Width;
m_Height = Height;
m_BPP = BPP;
m_Rows = new uint8_t *[Height];
if(m_Rows == NULL)
return false;
memset(m_Rows, 0, sizeof(uint8_t *) * Height);
return true;
}
uint8_t *RowBuffer::GetRow(int Row)
{
assert(m_BPP > 0);
if(m_Rows[Row] == NULL)
{
m_Rows[Row] = new uint8_t[m_Width*m_BPP];
if(m_Rows[Row] == NULL)
return NULL;
if(m_StartRow == m_EndRow)
{
m_StartRow = Row;
m_EndRow = m_StartRow + 1;
}
}
if(int(Row) == m_StartRow+1)
{
while(m_StartRow != 0 && m_Rows[m_StartRow-1])
--m_StartRow;
}
if(int(Row) == m_EndRow)
{
while(m_EndRow < m_Height && m_Rows[m_EndRow])
++m_EndRow;
}
return m_Rows[Row];
}
void RowBuffer::DiscardRows(int DiscardRow)
{
assert(m_BPP > 0);
for(int i = m_StartRow; i < DiscardRow; ++i)
{
delete [] m_Rows[i];
m_Rows[i] = NULL;
}
m_StartRow = max(m_StartRow, DiscardRow);
m_EndRow = max(m_EndRow, DiscardRow);
}

View File

@@ -0,0 +1,40 @@
#ifndef ROW_BUFFER_H
#define ROW_BUFFER_H
#include <stdint.h>
class RowBuffer
{
public:
RowBuffer();
~RowBuffer();
bool Init(int Width, int Height, int BPP);
/* Return row, allocating if necessary. */
uint8_t *GetRow(int row);
// Free rows [0,DiscardRow).
void DiscardRows(int DiscardRow);
/* Get a range of rows allocated in m_Rows: [m_StartRow,m_EndRow). If
* more than one allocated range exists, which range is returned is undefined. */
int GetStartRow() const { return m_StartRow; }
int GetEndRow() const { return m_EndRow; }
const uint8_t *const *GetRows() const { return m_Rows; }
private:
/* Array of image rows. These are allocated as needed. */
uint8_t **m_Rows;
/* in m_Rows is allocated: */
int m_StartRow;
int m_EndRow;
int m_Width;
int m_Height;
int m_BPP;
};
#endif

View File

@@ -0,0 +1,84 @@
#include <ruby.h>
#include <stdio.h>
#include <string.h>
#include "PNGReader.h"
#include "GIFReader.h"
#include "JPEGReader.h"
#include "Resize.h"
static VALUE danbooru_module;
static VALUE danbooru_resize_image(VALUE module, VALUE file_ext_val, VALUE read_path_val, VALUE write_path_val, VALUE output_width_val, VALUE output_height_val, VALUE output_quality_val)
{
const char * file_ext = StringValueCStr(file_ext_val);
const char * read_path = StringValueCStr(read_path_val);
const char * write_path = StringValueCStr(write_path_val);
int output_width = NUM2INT(output_width_val);
int output_height = NUM2INT(output_height_val);
int output_quality = NUM2INT(output_quality_val);
FILE *read_file = fopen(read_path, "rb");
if(read_file == NULL)
rb_raise(rb_eIOError, "can't open %s\n", read_path);
FILE *write_file = fopen(write_path, "wb");
if(write_file == NULL)
{
fclose(read_file);
rb_raise(rb_eIOError, "can't open %s\n", write_path);
}
bool ret = false;
char error[1024];
JPEGCompressor *Compressor = NULL;
Resizer *resizer = NULL;
Reader *Reader = NULL;
if (!strcmp(file_ext, "jpg") || !strcmp(file_ext, "jpeg"))
Reader = new JPEG;
else if (!strcmp(file_ext, "gif"))
Reader = new GIF;
else if (!strcmp(file_ext, "png"))
Reader = new PNG;
else
{
strcpy(error, "unknown filetype");
goto cleanup;
}
Compressor = new JPEGCompressor(write_file);
if(Compressor == NULL)
{
strcpy(error, "out of memory");
goto cleanup;
}
resizer = new Resizer(Compressor);
if(resizer == NULL || Reader == NULL)
{
strcpy(error, "out of memory");
goto cleanup;
}
resizer->SetDest(output_width, output_height, output_quality);
ret = Reader->Read(read_file, resizer, error);
cleanup:
delete Reader;
delete resizer;
delete Compressor;
fclose(read_file);
fclose(write_file);
if(!ret)
rb_raise(rb_eException, "%s", error);
return INT2FIX(0);
}
extern "C" void Init_danbooru_image_resizer() {
danbooru_module = rb_define_module("Danbooru");
rb_define_module_function(danbooru_module, "resize_image", (VALUE(*)(...))danbooru_resize_image, 6);
}

View File

@@ -0,0 +1,29 @@
require 'danbooru_image_resizer/danbooru_image_resizer.so'
module Danbooru
def resize(file_ext, read_path, write_path, output_size, output_quality)
Danbooru.resize_image(file_ext, read_path, write_path, output_size[:width], output_size[:height], output_quality)
end
def reduce_to(size, max_size)
returning size.dup do |new_size|
if new_size[:width] > max_size[:width]
scale = max_size[:width].to_f / new_size[:width].to_f
new_size[:width] = new_size[:width] * scale
new_size[:height] = new_size[:height] * scale
end
if max_size[:height] && (new_size[:height] > max_size[:height])
scale = max_size[:height].to_f / new_size[:height].to_f
new_size[:width] = new_size[:width] * scale
new_size[:height] = new_size[:height] * scale
end
new_size[:width] = new_size[:width].to_i
new_size[:height] = new_size[:height].to_i
end
end
module_function :resize
module_function :reduce_to
end

View File

@@ -0,0 +1,26 @@
#!/bin/env ruby
require 'mkmf'
CONFIG['CC'] = "g++"
CONFIG['LDSHARED'] = CONFIG['LDSHARED'].sub(/^cc /,'g++ ') # otherwise we would not link with the C++ runtime
dir_config("gd")
dir_config("jpeg")
dir_config("png")
have_header("gd.h")
have_library("gd")
have_library("jpeg")
have_library("png")
have_func("gdImageCreateFromGif", "gd.h")
have_func("gdImageJpeg", "gd.h")
have_func("jpeg_set_quality", ["stdlib.h", "stdio.h", "jpeglib-extern.h"])
have_func("png_set_expand_gray_1_2_4_to_8", "png.h")
with_cflags("-O2 -fno-exceptions -Wall") {true}
#with_cflags("-O0 -g -fno-exceptions -Wall") {true}
create_makefile("danbooru_image_resizer")

View File

@@ -0,0 +1,16 @@
// Needed for OS X
#ifndef JPEGLIB_EXTERN_H
#define JPEGLIB_EXTERN_H
#ifdef __cplusplus
extern "C" {
#endif
#include <jpeglib.h>
#ifdef __cplusplus
}
#endif
#endif