From 97134c3268ffd31999924b237f7d4ae98e25351d Mon Sep 17 00:00:00 2001 From: albert Date: Mon, 22 Aug 2011 17:46:20 -0400 Subject: [PATCH] updated resizer --- lib/danbooru_image_resizer/ConvertToRGB.cpp | 66 ++++ lib/danbooru_image_resizer/ConvertToRGB.h | 27 ++ lib/danbooru_image_resizer/Filter.h | 16 + lib/danbooru_image_resizer/GIFReader.cpp | 32 +- lib/danbooru_image_resizer/GIFReader.h | 3 +- lib/danbooru_image_resizer/Histogram.cpp | 48 +++ lib/danbooru_image_resizer/Histogram.h | 29 ++ lib/danbooru_image_resizer/JPEGReader.cpp | 77 ++-- lib/danbooru_image_resizer/JPEGReader.h | 13 +- lib/danbooru_image_resizer/Makefile | 43 +-- lib/danbooru_image_resizer/PNGReader.cpp | 17 +- lib/danbooru_image_resizer/PNGReader.h | 7 +- lib/danbooru_image_resizer/Reader.h | 4 +- lib/danbooru_image_resizer/Resize.cpp | 352 +++++++++++------- lib/danbooru_image_resizer/Resize.h | 43 ++- lib/danbooru_image_resizer/RowBuffer.cpp | 81 ---- lib/danbooru_image_resizer/RowBuffer.h | 111 +++++- .../danbooru_image_resizer.cpp | 141 +++++-- .../danbooru_image_resizer.rb | 31 +- lib/danbooru_image_resizer/extconf.rb | 2 +- lib/danbooru_image_resizer/test.rb | 5 + 21 files changed, 792 insertions(+), 356 deletions(-) create mode 100644 lib/danbooru_image_resizer/ConvertToRGB.cpp create mode 100644 lib/danbooru_image_resizer/ConvertToRGB.h create mode 100644 lib/danbooru_image_resizer/Filter.h create mode 100644 lib/danbooru_image_resizer/Histogram.cpp create mode 100644 lib/danbooru_image_resizer/Histogram.h delete mode 100644 lib/danbooru_image_resizer/RowBuffer.cpp create mode 100644 lib/danbooru_image_resizer/test.rb diff --git a/lib/danbooru_image_resizer/ConvertToRGB.cpp b/lib/danbooru_image_resizer/ConvertToRGB.cpp new file mode 100644 index 000000000..eb1e950d3 --- /dev/null +++ b/lib/danbooru_image_resizer/ConvertToRGB.cpp @@ -0,0 +1,66 @@ +#include +#include +#include +#include "ConvertToRGB.h" +#include "Filter.h" +#include +using namespace std; + +ConvertToRGB::ConvertToRGB(auto_ptr pCompressor): + m_pCompressor(pCompressor) +{ + m_pBuffer = NULL; +} + +ConvertToRGB::~ConvertToRGB() +{ + delete[] m_pBuffer; +} + +bool ConvertToRGB::Init(int iSourceWidth, int iSourceHeight, int iBPP) +{ + m_iSourceWidth = iSourceWidth; + // m_iSourceHeight = iSourceHeight; + m_iBPP = iBPP; + m_pBuffer = new uint8_t[iSourceWidth * 3]; + assert(m_iBPP == 1 || m_iBPP == 3 || m_iBPP == 4); // greyscale, RGB or RGBA + + return m_pCompressor->Init(iSourceWidth, iSourceHeight, 3); +} + +bool ConvertToRGB::WriteRow(uint8_t *pNewRow) +{ + if(m_iBPP == 3) + return m_pCompressor->WriteRow(pNewRow); + if(m_iBPP == 1) + { + uint8_t *pBuffer = m_pBuffer; + for(int i = 0; i < m_iSourceWidth; ++i) + { + *pBuffer++ = *pNewRow; + *pBuffer++ = *pNewRow; + *pBuffer++ = *pNewRow; + ++pNewRow; + } + } + else if(m_iBPP == 4) + { + uint8_t *pBuffer = m_pBuffer; + for(int i = 0; i < m_iSourceWidth; ++i) + { + uint8_t iR = *pNewRow++; + uint8_t iG = *pNewRow++; + uint8_t iB = *pNewRow++; + uint8_t iA = *pNewRow++; + iR = uint8_t((iR * iA) / 255.0f); + iG = uint8_t((iG * iA) / 255.0f); + iB = uint8_t((iB * iA) / 255.0f); + *pBuffer++ = iR; + *pBuffer++ = iG; + *pBuffer++ = iB; + } + } + + return m_pCompressor->WriteRow(m_pBuffer); +} + diff --git a/lib/danbooru_image_resizer/ConvertToRGB.h b/lib/danbooru_image_resizer/ConvertToRGB.h new file mode 100644 index 000000000..be117c90f --- /dev/null +++ b/lib/danbooru_image_resizer/ConvertToRGB.h @@ -0,0 +1,27 @@ +#ifndef CONVERT_TO_RGB_H +#define CONVERT_TO_RGB_H + +#include "Filter.h" +#include +using namespace std; + +class ConvertToRGB: public Filter +{ +public: + ConvertToRGB(auto_ptr pCompressor); + ~ConvertToRGB(); + + bool Init(int iSourceWidth, int iSourceHeight, int BPP); + bool WriteRow(uint8_t *pNewRow); + bool Finish() { return true; } + + const char *GetError() const { return NULL; } + +private: + uint8_t *m_pBuffer; + auto_ptr m_pCompressor; + int m_iSourceWidth; + int m_iBPP; +}; + +#endif diff --git a/lib/danbooru_image_resizer/Filter.h b/lib/danbooru_image_resizer/Filter.h new file mode 100644 index 000000000..03b1093e6 --- /dev/null +++ b/lib/danbooru_image_resizer/Filter.h @@ -0,0 +1,16 @@ +#ifndef FILTER_H +#define FILTER_H + +#include + +class Filter +{ +public: + virtual ~Filter() { } + virtual bool Init(int iSourceWidth, int iSourceHeight, int iSourceBPP) = 0; + virtual bool WriteRow(uint8_t *row) = 0; + virtual bool Finish() = 0; + virtual const char *GetError() const = 0; +}; + +#endif diff --git a/lib/danbooru_image_resizer/GIFReader.cpp b/lib/danbooru_image_resizer/GIFReader.cpp index f02744e78..b4bbc9eb4 100644 --- a/lib/danbooru_image_resizer/GIFReader.cpp +++ b/lib/danbooru_image_resizer/GIFReader.cpp @@ -1,12 +1,11 @@ +#include #include #include #include "GIFReader.h" -#include "RowBuffer.h" #include "Resize.h" -bool GIF::Read(FILE *f, Resizer *resizer, char error[1024]) +bool GIF::Read(FILE *f, Filter *pOutput, char error[1024]) { - RowBuffer Rows; bool Ret = false; gdImage *image = gdImageCreateFromGif(f); @@ -16,21 +15,18 @@ bool GIF::Read(FILE *f, Resizer *resizer, char error[1024]) return false; } - if(!Rows.Init(image->sx, image->sy, 3)) + uint8_t *pBuf = NULL; + pBuf = (uint8_t *) malloc(image->sx * 3); + if(pBuf == NULL) { strcpy(error, "out of memory"); goto cleanup; } - resizer->SetSource(image->sx, image->sy, 3); + pOutput->Init(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; - } + uint8_t *p = pBuf; for(int x = 0; x < image->sx; ++x) { @@ -40,19 +36,25 @@ bool GIF::Read(FILE *f, Resizer *resizer, char error[1024]) (*p++) = gdTrueColorGetBlue(c); } - int DiscardRow; - if(!resizer->Run(Rows.GetRows(), Rows.GetStartRow(), Rows.GetEndRow(), DiscardRow)) + if(!pOutput->WriteRow(pBuf)) { - strcpy(error, resizer->GetError()); + strcpy(error, pOutput->GetError()); goto cleanup; } + } - Rows.DiscardRows(DiscardRow); + if(!pOutput->Finish()) + { + strcpy(error, pOutput->GetError()); + goto cleanup; } Ret = true; cleanup: + if(pBuf != NULL) + free(pBuf); + gdImageDestroy(image); return Ret; } diff --git a/lib/danbooru_image_resizer/GIFReader.h b/lib/danbooru_image_resizer/GIFReader.h index 61c200fa5..42488c1b1 100644 --- a/lib/danbooru_image_resizer/GIFReader.h +++ b/lib/danbooru_image_resizer/GIFReader.h @@ -2,10 +2,11 @@ #define GIF_READER_H #include "Reader.h" +class Filter; class GIF: public Reader { public: - bool Read(FILE *f, Resizer *resizer, char error[1024]); + bool Read(FILE *f, Filter *pOutput, char error[1024]); }; #endif diff --git a/lib/danbooru_image_resizer/Histogram.cpp b/lib/danbooru_image_resizer/Histogram.cpp new file mode 100644 index 000000000..49da2e107 --- /dev/null +++ b/lib/danbooru_image_resizer/Histogram.cpp @@ -0,0 +1,48 @@ +#include +#include +#include +#include +#include +#include "Histogram.h" +#include "Filter.h" +#include +using namespace std; + +Histogram::Histogram() +{ + memset(m_Histogram, 0, sizeof(m_Histogram)); +} + +bool Histogram::Init(int iSourceWidth, int iSourceHeight, int iBPP) +{ + assert(iBPP >= 3); + m_SourceWidth = iSourceWidth; + m_SourceBPP = iBPP; + + return true; +} +int Histogram::GetChannels() const +{ + return min(m_SourceBPP, 3); +} + +bool Histogram::WriteRow(uint8_t *pNewRow) +{ + uint8_t *pInput = pNewRow; + int channels = GetChannels(); + for(int x = 0; x < m_SourceWidth; ++x) + { + for(int c = 0; c < channels; ++c) + { + int color = pInput[c]; + if(m_SourceBPP == 3) + color = (color * pInput[3]) / 255; + ++m_Histogram[c][color]; + } + + pInput += m_SourceBPP; + } + + return true; +} + diff --git a/lib/danbooru_image_resizer/Histogram.h b/lib/danbooru_image_resizer/Histogram.h new file mode 100644 index 000000000..dbbef1ea5 --- /dev/null +++ b/lib/danbooru_image_resizer/Histogram.h @@ -0,0 +1,29 @@ +#ifndef HISTOGRAM_H +#define HISTOGRAM_H + +#include "Filter.h" +#include +using namespace std; +#include + +class Histogram: public Filter +{ +public: + Histogram(); + + bool Init(int iSourceWidth, int iSourceHeight, int BPP); + bool WriteRow(uint8_t *pNewRow); + bool Finish() { return true; } + + const char *GetError() const { return NULL; } + int GetChannels() const; + const unsigned *GetHistogram(int iChannel) const { return m_Histogram[iChannel]; } + +private: + unsigned m_Histogram[3][256]; + + int m_SourceWidth; + int m_SourceBPP; +}; + +#endif diff --git a/lib/danbooru_image_resizer/JPEGReader.cpp b/lib/danbooru_image_resizer/JPEGReader.cpp index e3d61eab9..d235940b9 100644 --- a/lib/danbooru_image_resizer/JPEGReader.cpp +++ b/lib/danbooru_image_resizer/JPEGReader.cpp @@ -1,6 +1,6 @@ #include +#include #include "JPEGReader.h" -#include "RowBuffer.h" #include "Resize.h" #include using namespace std; @@ -32,9 +32,14 @@ const char *JPEGCompressor::GetError() const return m_JErr.buffer; } - -bool JPEGCompressor::Init(int width, int height, int quality) +void JPEGCompressor::SetQuality(int quality) { + m_iQuality = quality; +} + +bool JPEGCompressor::Init(int width, int height, int bpp) +{ + assert(bpp == 3); m_CInfo.err = jpeg_std_error(&m_JErr.pub); m_JErr.pub.error_exit = jpeg_error_exit; @@ -53,8 +58,18 @@ bool JPEGCompressor::Init(int width, int height, int quality) 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_set_quality(&m_CInfo, m_iQuality, TRUE); // limit to baseline-JPEG values + + /* For high-quality compression, disable color subsampling. */ + if(m_iQuality >= 95) + { + m_CInfo.comp_info[0].h_samp_factor = 1; + m_CInfo.comp_info[0].v_samp_factor = 1; + m_CInfo.comp_info[1].h_samp_factor = 1; + m_CInfo.comp_info[1].v_samp_factor = 1; + m_CInfo.comp_info[2].h_samp_factor = 1; + m_CInfo.comp_info[2].v_samp_factor = 1; + } jpeg_start_compress(&m_CInfo, TRUE); @@ -89,13 +104,10 @@ bool JPEGCompressor::Finish() return true; } -bool JPEG::Read(FILE *f, Resizer *resizer, char error[1024]) +bool JPEG::Read(FILE *f, Filter *pOutput, char error[1024]) { // JMSG_LENGTH_MAX <= sizeof(error) - m_JErr.buffer = error; - RowBuffer Rows; - - m_Resizer = resizer; + m_pOutputFilter = pOutput; struct jpeg_decompress_struct CInfo; CInfo.err = jpeg_std_error(&m_JErr.pub); @@ -103,44 +115,55 @@ bool JPEG::Read(FILE *f, Resizer *resizer, char error[1024]) m_JErr.pub.emit_message = jpeg_warning; bool Ret = false; + uint8_t *pBuf = NULL; if(setjmp(m_JErr.setjmp_buffer)) + { + memcpy(error, m_JErr.buffer, JMSG_LENGTH_MAX); goto cleanup; + } jpeg_create_decompress(&CInfo); jpeg_stdio_src(&CInfo, f); jpeg_read_header(&CInfo, TRUE); CInfo.out_color_space = JCS_RGB; + if(CInfo.jpeg_color_space == JCS_CMYK || CInfo.jpeg_color_space == JCS_YCCK) + { + strcpy(error, "CMYK JPEGs are not supported; please convert to RGB"); + goto cleanup; + } jpeg_start_decompress(&CInfo); - if(!Rows.Init(CInfo.output_width, CInfo.output_height, 3)) + if(!m_pOutputFilter->Init(CInfo.output_width, CInfo.output_height, 3)) + { + strncpy(error, m_pOutputFilter->GetError(), sizeof(error)); + error[sizeof(error)-1] = 0; + goto cleanup; + } + + pBuf = (uint8_t *) malloc(CInfo.output_width * 3); + if(pBuf == NULL) { 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) + jpeg_read_scanlines(&CInfo, &pBuf, 1); + + if(!m_pOutputFilter->WriteRow(pBuf)) { - strcpy(error, "out of memory"); + strcpy(error, m_pOutputFilter->GetError()); 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); + if(!m_pOutputFilter->Finish()) + { + strcpy(error, m_pOutputFilter->GetError()); + goto cleanup; } jpeg_finish_decompress(&CInfo); @@ -148,6 +171,8 @@ bool JPEG::Read(FILE *f, Resizer *resizer, char error[1024]) Ret = true; cleanup: + if(pBuf != NULL) + free(pBuf); jpeg_destroy_decompress(&CInfo); return Ret; diff --git a/lib/danbooru_image_resizer/JPEGReader.h b/lib/danbooru_image_resizer/JPEGReader.h index 14479979e..06c8625c6 100644 --- a/lib/danbooru_image_resizer/JPEGReader.h +++ b/lib/danbooru_image_resizer/JPEGReader.h @@ -6,31 +6,33 @@ #include #include "jpeglib-extern.h" #include "Reader.h" +#include "Filter.h" struct jpeg_error { struct jpeg_error_mgr pub; jmp_buf setjmp_buffer; - char *buffer; + char buffer[JMSG_LENGTH_MAX]; }; class JPEG: public Reader { public: - bool Read(FILE *f, Resizer *resizer, char error[1024]); + bool Read(FILE *f, Filter *pOutput, char error[1024]); private: - Resizer *m_Resizer; + Filter *m_pOutputFilter; struct jpeg_error m_JErr; }; -class JPEGCompressor +class JPEGCompressor: public Filter { public: JPEGCompressor(FILE *f); ~JPEGCompressor(); - bool Init(int width, int height, int quality); + bool Init(int iSourceWidth, int iSourceHeight, int iBPP); + void SetQuality(int quality); bool WriteRow(uint8_t *row); bool Finish(); @@ -40,6 +42,7 @@ public: private: FILE *m_File; + int m_iQuality; struct jpeg_compress_struct m_CInfo; struct jpeg_error m_JErr; }; diff --git a/lib/danbooru_image_resizer/Makefile b/lib/danbooru_image_resizer/Makefile index 88a6fd1ac..b4690b847 100644 --- a/lib/danbooru_image_resizer/Makefile +++ b/lib/danbooru_image_resizer/Makefile @@ -4,11 +4,11 @@ SHELL = /bin/sh #### Start of system configuration section. #### srcdir = . -topdir = /Users/ayi/.rvm/rubies/ruby-1.9.2-preview1/include/ruby-1.9.1 -hdrdir = /Users/ayi/.rvm/rubies/ruby-1.9.2-preview1/include/ruby-1.9.1 -arch_hdrdir = /Users/ayi/.rvm/rubies/ruby-1.9.2-preview1/include/ruby-1.9.1/$(arch) +topdir = /Users/ayi/.rvm/rubies/ruby-1.9.2-p0/include/ruby-1.9.1 +hdrdir = /Users/ayi/.rvm/rubies/ruby-1.9.2-p0/include/ruby-1.9.1 +arch_hdrdir = /Users/ayi/.rvm/rubies/ruby-1.9.2-p0/include/ruby-1.9.1/$(arch) VPATH = $(srcdir):$(arch_hdrdir)/ruby:$(hdrdir)/ruby -prefix = $(DESTDIR)/Users/ayi/.rvm/rubies/ruby-1.9.2-preview1 +prefix = $(DESTDIR)/Users/ayi/.rvm/rubies/ruby-1.9.2-p0 rubylibprefix = $(libdir)/$(RUBY_BASE_NAME) exec_prefix = $(prefix) vendorhdrdir = $(rubyhdrdir)/vendor_ruby @@ -45,7 +45,7 @@ vendorarchdir = $(vendorlibdir)/$(sitearch) CC = g++ CXX = g++ -LIBRUBY = $(LIBRUBY_A) +LIBRUBY = $(LIBRUBY_SO) LIBRUBY_A = lib$(RUBY_SO_NAME)-static.a LIBRUBYARG_SHARED = -l$(RUBY_SO_NAME) LIBRUBYARG_STATIC = -l$(RUBY_SO_NAME)-static @@ -55,29 +55,29 @@ COUTFLAG = -o RUBY_EXTCONF_H = cflags = $(optflags) $(debugflags) $(warnflags) optflags = -O3 -debugflags = -g -warnflags = -Wall -Wno-unused-parameter -Wno-parentheses -Wno-missing-field-initializers -Wshorten-64-to-32 -Wpointer-arith -Wwrite-strings -CFLAGS = -fno-common -O2 -fno-exceptions -Wall +debugflags = -ggdb +warnflags = -Wextra -Wno-unused-parameter -Wno-parentheses -Wpointer-arith -Wwrite-strings -Wno-missing-field-initializers -Wshorten-64-to-32 -Wno-long-long +CFLAGS = -fno-common -O2 -Wall INCFLAGS = -I. -I$(arch_hdrdir) -I$(hdrdir)/ruby/backward -I$(hdrdir) -I$(srcdir) 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 $(DEFS) $(cppflags) CXXFLAGS = $(CFLAGS) $(cxxflags) ldflags = -L. -dldflags = -archflag = -DLDFLAGS = $(ldflags) $(dldflags) $(archflag) -LDSHARED = $(CC) -dynamic -bundle -undefined suppress -flat_namespace -LDSHAREDXX = $(CXX) -dynamic -bundle -undefined suppress -flat_namespace +dldflags = -Wl,-undefined,dynamic_lookup -Wl,-multiply_defined,suppress -Wl,-flat_namespace +ARCH_FLAG = +DLDFLAGS = $(ldflags) $(dldflags) +LDSHARED = $(CC) -dynamic -bundle +LDSHAREDXX = $(CXX) -dynamic -bundle AR = ar EXEEXT = RUBY_BASE_NAME = ruby RUBY_INSTALL_NAME = ruby -RUBY_SO_NAME = ruby -arch = i386-darwin10.4.0 +RUBY_SO_NAME = ruby.1.9.1 +arch = x86_64-darwin10.4.0 sitearch = $(arch) ruby_version = 1.9.1 -ruby = /Users/ayi/.rvm/rubies/ruby-1.9.2-preview1/bin/ruby +ruby = /Users/ayi/.rvm/rubies/ruby-1.9.2-p0/bin/ruby RUBY = $(ruby) RM = rm -f RM_RF = $(RUBY) -run -e rm -- -rf @@ -104,9 +104,9 @@ extout = extout_prefix = target_prefix = LOCAL_LIBS = -LIBS = -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 +LIBS = $(LIBRUBYARG_SHARED) -lpng -ljpeg -lgd -lpthread -ldl -lobjc +SRCS = ConvertToRGB.cpp danbooru_image_resizer.cpp GIFReader.cpp Histogram.cpp JPEGReader.cpp PNGReader.cpp Resize.cpp +OBJS = ConvertToRGB.o danbooru_image_resizer.o GIFReader.o Histogram.o JPEGReader.o PNGReader.o Resize.o TARGET = danbooru_image_resizer DLLIB = $(TARGET).bundle EXTSTATIC = @@ -147,8 +147,9 @@ install: install-so install-rb install-so: $(RUBYARCHDIR) install-so: $(RUBYARCHDIR)/$(DLLIB) -$(RUBYARCHDIR)/$(DLLIB): $(RUBYARCHDIR) $(DLLIB) - $(INSTALL_PROG) $(DLLIB) $(RUBYARCHDIR) +$(RUBYARCHDIR)/$(DLLIB): $(DLLIB) + @-$(MAKEDIRS) $(@D) + $(INSTALL_PROG) $(DLLIB) $(@D) install-rb: pre-install-rb install-rb-default install-rb-default: pre-install-rb-default pre-install-rb: Makefile diff --git a/lib/danbooru_image_resizer/PNGReader.cpp b/lib/danbooru_image_resizer/PNGReader.cpp index d65886f19..7fa1bd7a7 100644 --- a/lib/danbooru_image_resizer/PNGReader.cpp +++ b/lib/danbooru_image_resizer/PNGReader.cpp @@ -44,7 +44,7 @@ void PNG::InfoCallback(png_struct *png, png_info *info_ptr) png_read_update_info(png, info_ptr); - data->m_Resizer->SetSource(width, height, 4); + data->m_pOutputFilter->Init(width, height, 4); } void PNG::RowCallback(png_struct *png, png_byte *new_row, png_uint_32 row_num, int pass) @@ -62,14 +62,12 @@ void PNG::RowCallback(png_struct *png, png_byte *new_row, png_uint_32 row_num, i /* 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(!data->m_pOutputFilter->WriteRow(p)) + Error(png, data->m_pOutputFilter->GetError()); /* If we're interlaced, never discard rows. */ if(data->m_Passes == 1) - data->m_Rows.DiscardRows(DiscardRow); + data->m_Rows.DiscardRows(row_num+1); } void PNG::EndCallback(png_struct *png, png_info *info) @@ -79,9 +77,9 @@ void PNG::EndCallback(png_struct *png, png_info *info) } -bool PNG::Read(FILE *f, Resizer *resizer, char error[1024]) +bool PNG::Read(FILE *f, Filter *pOutput, char error[1024]) { - m_Resizer = resizer; + m_pOutputFilter = pOutput; png_error_info err; err.err = error; @@ -125,6 +123,9 @@ bool PNG::Read(FILE *f, Resizer *resizer, char error[1024]) png_process_data(png, info_ptr, buf, ret); } + if(!m_pOutputFilter->Finish()) + Error(png, m_pOutputFilter->GetError()); + if(!m_Done) { strcpy(error, "incomplete file"); diff --git a/lib/danbooru_image_resizer/PNGReader.h b/lib/danbooru_image_resizer/PNGReader.h index 5eeabc847..a065152d3 100644 --- a/lib/danbooru_image_resizer/PNGReader.h +++ b/lib/danbooru_image_resizer/PNGReader.h @@ -3,6 +3,7 @@ #include #include "Reader.h" +#include "Filter.h" #include "RowBuffer.h" struct png_error_info @@ -18,11 +19,11 @@ public: m_Done = false; } - bool Read(FILE *f, Resizer *resizer, char error[1024]); + bool Read(FILE *f, Filter *pOutput, char error[1024]); private: - RowBuffer m_Rows; - Resizer *m_Resizer; + RowBuffer m_Rows; + Filter *m_pOutputFilter; bool m_Done; int m_Passes; diff --git a/lib/danbooru_image_resizer/Reader.h b/lib/danbooru_image_resizer/Reader.h index c7b6892f6..eb217ccfd 100644 --- a/lib/danbooru_image_resizer/Reader.h +++ b/lib/danbooru_image_resizer/Reader.h @@ -3,12 +3,12 @@ #include -class Resizer; +class Filter; class Reader { public: virtual ~Reader() { } - virtual bool Read(FILE *f, Resizer *rp, char errorbuf[1024]) = 0; + virtual bool Read(FILE *f, Filter *rp, char errorbuf[1024]) = 0; }; #endif diff --git a/lib/danbooru_image_resizer/Resize.cpp b/lib/danbooru_image_resizer/Resize.cpp index a6ad03fef..353f0b8ae 100644 --- a/lib/danbooru_image_resizer/Resize.cpp +++ b/lib/danbooru_image_resizer/Resize.cpp @@ -1,17 +1,101 @@ #include #include #include +#include #include #include "Resize.h" -#include "JPEGReader.h" +#include "Filter.h" #include using namespace std; -Resizer::Resizer(JPEGCompressor *Compressor) +namespace { - m_Compressor = Compressor; + inline float sincf(float x) + { + if(fabsf(x) < 1e-9) + return 1.0; + + return sinf(x) / x; + } + + inline double fract(double f) + { + return f - floor(f); + } +} + +static const int KERNEL_SIZE = 3; + +LanczosFilter::LanczosFilter() +{ + m_pFilters = NULL; +} + +LanczosFilter::~LanczosFilter() +{ + delete[] m_pFilters; +} + +void LanczosFilter::Init(float fFactor) +{ + /* If we're reducing the image, each output pixel samples each input pixel in the + * range once, so we step one pixel. If we're enlarging it by 2x, each output pixel + * samples each input pixel twice, so we step half a pixel. */ + m_fStep = 1; + if(fFactor > 1.0) + m_fStep = 1.0 / fFactor; + + /* If we're sampling each pixel twice (m_fStep is .5), then we need twice as many taps + * to sample KERNEL_SIZE pixels. */ + m_iTaps = (int) ceil(KERNEL_SIZE / m_fStep) * 2; + + delete[] m_pFilters; + m_pFilters = NULL; // in case of exception + m_pFilters = new float[m_iTaps * 256]; + + float *pOutput = m_pFilters; + for(int i=0; i < 256; ++i) + { + float fOffset = i / 256.0f; + + float fSum = 0; + for(int i = 0; i < m_iTaps; ++i) + { + float fPos = -(m_iTaps/2-1) - fOffset + i; + fPos *= m_fStep; + + float fValue = 0; + if(fabs(fPos) < KERNEL_SIZE) + fValue = sincf(M_PI*fPos) * sincf(M_PI / KERNEL_SIZE * fPos); + + pOutput[i] = fValue; + fSum += fValue; + } + + /* Scale the filter so it sums to 1. */ + for(int i = 0; i pOutput): + m_pCompressor(pOutput) +{ + m_DestWidth = -1; + m_DestHeight = -1; m_CurrentY = 0; m_OutBuf = NULL; + m_szError = NULL; + m_iInputY = 0; } Resizer::~Resizer() @@ -22,160 +106,178 @@ Resizer::~Resizer() const char *Resizer::GetError() const { - return m_Compressor->GetError(); + if(m_szError != NULL) + return m_szError; + return m_pCompressor->GetError(); } -void Resizer::SetSource(int Width, int Height, int BPP) +bool Resizer::Init(int iSourceWidth, int iSourceHeight, int iBPP) { - m_SourceWidth = Width; - m_SourceHeight = Height; - m_SourceBPP = BPP; -} + assert(m_DestWidth != -1); + assert(m_DestHeight != -1); + assert(iBPP == 3); + m_SourceWidth = iSourceWidth; + m_SourceHeight = iSourceHeight; + m_SourceBPP = iBPP; -bool Resizer::SetDest(int Width, int Height, int Quality) -{ - m_DestWidth = Width; - m_DestHeight = Height; - m_OutBuf = (uint8_t *) malloc(Width*3); + float fXFactor = float(m_SourceWidth) / m_DestWidth; + m_XFilter.Init(fXFactor); - return m_Compressor->Init(Width, Height, Quality); -} + float fYFactor = float(m_SourceHeight) / m_DestHeight; + m_YFilter.Init(fYFactor); -#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) + if(!m_Rows.Init(m_DestWidth, m_SourceHeight, m_SourceBPP, m_YFilter.m_iTaps)) { - 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; - } + m_szError = "out of memory"; + return false; } - if(Total != 0.0f) - for(int i = 0; i < 4; ++i) - Colors[i] /= Total; + m_OutBuf = (uint8_t *) malloc(m_DestWidth * m_SourceBPP); + if(m_OutBuf == NULL) + { + m_szError = "out of memory"; + return false; + } + + return m_pCompressor->Init(m_DestWidth, m_DestHeight, m_SourceBPP); } -bool Resizer::Run(const uint8_t *const *Source, int StartRow, int EndRow, int &DiscardRow) +void Resizer::SetDest(int iDestWidth, int iDestHeight) { - while(m_CurrentY < m_DestHeight) + m_DestWidth = iDestWidth; + m_DestHeight = iDestHeight; +} + +static uint8_t *PadRow(const uint8_t *pSourceRow, int iWidth, int iBPP, int iPadding) +{ + uint8_t *pRow = new uint8_t[(iWidth + iPadding*2) * iBPP]; + uint8_t *pDest = pRow; + for(int x = 0; x < iPadding; ++x) { - 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; + for(int i = 0; i < iBPP; ++i) + pDest[i] = pSourceRow[i]; + pDest += iBPP; + } - if(EndRow != m_SourceHeight && int(SourceYEnd)+1 > EndRow-1) + memcpy(pDest, pSourceRow, iWidth*iBPP*sizeof(uint8_t)); + pDest += iWidth*iBPP; + + for(int x = 0; x < iPadding; ++x) + { + for(int i = 0; i < iBPP; ++i) + pDest[i] = pSourceRow[i]; + pDest += iBPP; + } + + return pRow; +} + +bool Resizer::WriteRow(uint8_t *pNewRow) +{ + if(m_SourceWidth == m_DestWidth && m_SourceHeight == m_DestHeight) + { + ++m_CurrentY; + + /* We don't actually have any resizing to do, so short-circuit. */ + if(!m_pCompressor->WriteRow((uint8_t *) pNewRow)) + return false; + + if(m_CurrentY != m_DestHeight) return true; - assert(SourceYStart>=StartRow); - uint8_t *Output = m_OutBuf; + return m_pCompressor->Finish(); + } + + /* Make a copy of pNewRow with the first and last pixel duplicated, so we don't have to do + * bounds checking in the inner loop below. */ + uint8_t *pActualPaddedRow = PadRow(pNewRow, m_SourceWidth, m_SourceBPP, m_XFilter.m_iTaps/2); + const uint8_t *pPaddedRow = pActualPaddedRow + (m_XFilter.m_iTaps/2)*m_SourceBPP; + + const float fXFactor = float(m_SourceWidth) / m_DestWidth; + const float fYFactor = float(m_SourceHeight) / m_DestHeight; + + /* Run the horizontal filter on the incoming row, and drop the result into m_Rows. */ + { + float *pRow = m_Rows.GetRow(m_iInputY); + ++m_iInputY; + + float *pOutput = pRow; 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); + const double fSourceX = (x + 0.5f) * fXFactor; + const double fOffset = fract(fSourceX + 0.5); + const float *pFilter = m_XFilter.GetFilter(fOffset); + const int iStartX = lrint(fSourceX - m_XFilter.m_iTaps/2 + 1e-6); - float Colors[4] = { 0.0 }; - Average(Source, Colors, SourceXStart, SourceXEnd, SourceYStart, SourceYEnd, m_SourceBPP); + const uint8_t *pSource = pPaddedRow + iStartX*3; - if(m_SourceBPP == 4) + float fR = 0, fG = 0, fB = 0; + for(int i = 0; i < m_XFilter.m_iTaps; ++i) { - for(int i = 0; i < 3; ++i) - Colors[i] *= Colors[3]/255.0f; + float fWeight = *pFilter++; + + fR += pSource[0] * fWeight; + fG += pSource[1] * fWeight; + fB += pSource[2] * fWeight; + pSource += 3; } - 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])); + pOutput[0] = fR; + pOutput[1] = fG; + pOutput[2] = fB; - Output += 3; + pOutput += m_SourceBPP; + } + } + delete[] pActualPaddedRow; + + const float *const *pSourceRows = m_Rows.GetRows(); + while(m_CurrentY < m_DestHeight) + { + const double fSourceY = (m_CurrentY + 0.5) * fYFactor; + const double fOffset = fract(fSourceY + 0.5); + const int iStartY = lrint(fSourceY - m_YFilter.m_iTaps/2 + 1e-6); + + /* iStartY is the first row we'll need, and we never move backwards. Discard rows + * before it to save memory. */ + m_Rows.DiscardRows(iStartY); + + if(m_iInputY != m_SourceHeight && iStartY+m_YFilter.m_iTaps >= m_iInputY) + return true; + + /* Process the next output row. */ + uint8_t *pOutput = m_OutBuf; + for(int x = 0; x < m_DestWidth; ++x) + { + const float *pFilter = m_YFilter.GetFilter(fOffset); + + float fR = 0, fG = 0, fB = 0; + for(int i = 0; i < m_YFilter.m_iTaps; ++i) + { + const float *pSource = pSourceRows[iStartY+i]; + pSource += x * m_SourceBPP; + + float fWeight = *pFilter++; + fR += pSource[0] * fWeight; + fG += pSource[1] * fWeight; + fB += pSource[2] * fWeight; + } + + pOutput[0] = (uint8_t) max(0, min(255, (int) lrintf(fR))); + pOutput[1] = (uint8_t) max(0, min(255, (int) lrintf(fG))); + pOutput[2] = (uint8_t) max(0, min(255, (int) lrintf(fB))); + + pOutput += 3; } - if(!m_Compressor->WriteRow((JSAMPLE *) m_OutBuf)) + if(!m_pCompressor->WriteRow((uint8_t *) m_OutBuf)) return false; ++m_CurrentY; } if(m_CurrentY == m_DestHeight) { - if(!m_Compressor->Finish()) + if(!m_pCompressor->Finish()) return false; } diff --git a/lib/danbooru_image_resizer/Resize.h b/lib/danbooru_image_resizer/Resize.h index fb1090a8e..946daada9 100644 --- a/lib/danbooru_image_resizer/Resize.h +++ b/lib/danbooru_image_resizer/Resize.h @@ -1,32 +1,43 @@ #ifndef RESIZE_H #define RESIZE_H +#include "RowBuffer.h" +#include "Filter.h" +#include +using namespace std; #include -class JPEGCompressor; +struct LanczosFilter +{ + LanczosFilter(); + ~LanczosFilter(); + void Init(float fFactor); + const float *GetFilter(float fOffset) const; -class Resizer + float m_fStep; + int m_iTaps; + float *m_pFilters; +}; + +class Resizer: public Filter { public: - Resizer(JPEGCompressor *Compressor); + Resizer(auto_ptr pCompressor); ~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); + bool Init(int iSourceWidth, int iSourceHeight, int BPP); + void SetDest(int iDestWidth, int iDestHeight); + bool WriteRow(uint8_t *pNewRow); + bool Finish() { return true; } - /* - * 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; + auto_ptr m_pCompressor; uint8_t *m_OutBuf; + RowBuffer m_Rows; + const char *m_szError; int m_SourceWidth; int m_SourceHeight; @@ -35,7 +46,11 @@ private: int m_DestWidth; int m_DestHeight; - float m_CurrentY; + LanczosFilter m_XFilter; + LanczosFilter m_YFilter; + + int m_iInputY; + int m_CurrentY; }; #endif diff --git a/lib/danbooru_image_resizer/RowBuffer.cpp b/lib/danbooru_image_resizer/RowBuffer.cpp deleted file mode 100644 index e09179990..000000000 --- a/lib/danbooru_image_resizer/RowBuffer.cpp +++ /dev/null @@ -1,81 +0,0 @@ -#include -#include -#include -#include "RowBuffer.h" -#include -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); -} diff --git a/lib/danbooru_image_resizer/RowBuffer.h b/lib/danbooru_image_resizer/RowBuffer.h index 2c792236f..2c9618302 100644 --- a/lib/danbooru_image_resizer/RowBuffer.h +++ b/lib/danbooru_image_resizer/RowBuffer.h @@ -2,30 +2,126 @@ #define ROW_BUFFER_H #include +#include +#include +#include +#include "RowBuffer.h" +#include +using namespace std; +template class RowBuffer { public: - RowBuffer(); - ~RowBuffer(); + RowBuffer() + { + m_Rows = NULL; + m_ActualRows = NULL; + m_StartRow = 0; + m_EndRow = 0; + m_BPP = 0; + m_Height = 0; + } - bool Init(int Width, int Height, int BPP); + ~RowBuffer() + { + for(int i = 0; i < m_Height; ++i) + delete [] m_Rows[i]; + + delete [] m_ActualRows; + } + + /* + * If iVertPadding is non-zero, simulate padding on the top and bottom of the image. After + * row 0 is written, rows [-1 ... -iVertPadding] will point to the same row. After the bottom + * row is written, the following iVertPadding will also point to the last row. These rows + * are discarded when the row they refer to is discarded. + */ + bool Init(int iWidth, int iHeight, int iBPP, int iVertPadding = 0) + { + m_Width = iWidth; + m_Height = iHeight; + m_BPP = iBPP; + m_iVertPadding = iVertPadding; + + m_ActualRows = new T *[iHeight + iVertPadding*2]; + m_Rows = m_ActualRows + iVertPadding; + memset(m_ActualRows, 0, sizeof(T *) * (iHeight + iVertPadding*2)); + + return true; + } /* Return row, allocating if necessary. */ - uint8_t *GetRow(int row); + T *GetRow(int Row) + { + assert(m_BPP > 0); + + if(m_Rows[Row] == NULL) + { + m_Rows[Row] = new T[m_Width*m_BPP]; + if(Row == 0) + { + for(int i = -m_iVertPadding; i < 0; ++i) + m_Rows[i] = m_Rows[0]; + } + if(Row == m_Height - 1) + { + for(int i = m_Height; i < m_Height + m_iVertPadding; ++i) + m_Rows[i] = m_Rows[m_Height - 1]; + } + 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]; + } // Free rows [0,DiscardRow). - void DiscardRows(int DiscardRow); + void DiscardRows(int DiscardRow) + { + assert(m_BPP > 0); + if(DiscardRow > m_Height) + DiscardRow = m_Height; + + 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); + } /* 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; } + const T *const *GetRows() const { return m_Rows; } private: /* Array of image rows. These are allocated as needed. */ - uint8_t **m_Rows; + T **m_Rows; + + /* The actual pointer m_Rows is contained in. m_Rows may be offset from this to + * implement padding. */ + T **m_ActualRows; /* in m_Rows is allocated: */ int m_StartRow; @@ -34,6 +130,7 @@ private: int m_Width; int m_Height; int m_BPP; + int m_iVertPadding; }; #endif diff --git a/lib/danbooru_image_resizer/danbooru_image_resizer.cpp b/lib/danbooru_image_resizer/danbooru_image_resizer.cpp index 8c502450a..cca12b6e0 100644 --- a/lib/danbooru_image_resizer/danbooru_image_resizer.cpp +++ b/lib/danbooru_image_resizer/danbooru_image_resizer.cpp @@ -1,15 +1,31 @@ #include #include #include - +#include +using namespace std; #include "PNGReader.h" #include "GIFReader.h" #include "JPEGReader.h" #include "Resize.h" +#include "Histogram.h" +#include "ConvertToRGB.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) +static Reader *GetReader(const char *file_ext) +{ + if (!strcmp(file_ext, "jpg") || !strcmp(file_ext, "jpeg")) + return new JPEG; + if (!strcmp(file_ext, "gif")) + return new GIF; + if (!strcmp(file_ext, "png")) + return new PNG; + return NULL; +} + +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); @@ -31,44 +47,44 @@ static VALUE danbooru_resize_image(VALUE module, VALUE file_ext_val, VALUE read_ 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 + try { - strcpy(error, "unknown filetype"); - goto cleanup; - } + auto_ptr pReader(GetReader(file_ext)); + if(pReader.get() == NULL) + { + strcpy(error, "unknown filetype"); + goto cleanup; + } - Compressor = new JPEGCompressor(write_file); - if(Compressor == NULL) + auto_ptr pFilter(NULL); + + { + auto_ptr pCompressor(new JPEGCompressor(write_file)); + pCompressor->SetQuality(output_quality); + pFilter.reset(pCompressor.release()); + } + + { + auto_ptr pResizer(new Resizer(pFilter)); + pResizer->SetDest(output_width, output_height); + pFilter.reset(pResizer.release()); + } + + + { + auto_ptr pConverter(new ConvertToRGB(pFilter)); + pFilter.reset(pConverter.release()); + } + + ret = pReader->Read(read_file, pFilter.get(), error); + } + catch(const std::bad_alloc &e) { 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); @@ -78,7 +94,68 @@ cleanup: return INT2FIX(0); } +static VALUE danbooru_histogram(VALUE module, VALUE file_ext_val, VALUE read_path_val) +{ + const char * file_ext = StringValueCStr(file_ext_val); + const char * read_path = StringValueCStr(read_path_val); + + FILE *read_file = fopen(read_path, "rb"); + if(read_file == NULL) + rb_raise(rb_eIOError, "can't open %s\n", read_path); + + bool ret = false; + char error[1024]; + VALUE results = Qnil; + + try + { + auto_ptr pReader(GetReader(file_ext)); + if(pReader.get() == NULL) + { + strcpy(error, "unknown filetype"); + goto cleanup; + } + + auto_ptr pFilter(NULL); + + Histogram *pHistogram = new Histogram(); + pFilter.reset(pHistogram); + + { + auto_ptr pConverter(new ConvertToRGB(pFilter)); + pFilter.reset(pConverter.release()); + } + + ret = pReader->Read(read_file, pFilter.get(), error); + + results = rb_ary_new(); + int channels = pHistogram->GetChannels(); + for(int channel = 0; channel < channels; ++channel) + { + const unsigned *pChannelData = pHistogram->GetHistogram(channel); + VALUE channel_array = rb_ary_new(); + rb_ary_push(results, channel_array); + + for(int i = 0; i < 256; ++i) + rb_ary_push(channel_array, INT2NUM(pChannelData[i])); + } + } + catch(const std::bad_alloc &e) + { + strcpy(error, "out of memory"); + } + +cleanup: + fclose(read_file); + + if(!ret) + rb_raise(rb_eException, "%s", error); + + return results; +} + 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); + rb_define_module_function(danbooru_module, "histogram", (VALUE(*)(...))danbooru_histogram, 2); } diff --git a/lib/danbooru_image_resizer/danbooru_image_resizer.rb b/lib/danbooru_image_resizer/danbooru_image_resizer.rb index 68f718ee2..0f4c8f628 100644 --- a/lib/danbooru_image_resizer/danbooru_image_resizer.rb +++ b/lib/danbooru_image_resizer/danbooru_image_resizer.rb @@ -5,23 +5,24 @@ module Danbooru Danbooru.resize_image(file_ext, read_path, write_path, output_size[:width], output_size[:height], output_quality) end - def reduce_to(size, max_size) - size.dup.tap 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 + def reduce_to(size, max_size, ratio = 1) + ret = size.dup - new_size[:width] = new_size[:width].to_i - new_size[:height] = new_size[:height].to_i + if ret[:width] > ratio * max_size[:width] + scale = max_size[:width].to_f / ret[:width].to_f + ret[:width] = ret[:width] * scale + ret[:height] = ret[:height] * scale end + + if max_size[:height] && (ret[:height] > ratio * max_size[:height]) + scale = max_size[:height].to_f / ret[:height].to_f + ret[:width] = ret[:width] * scale + ret[:height] = ret[:height] * scale + end + + ret[:width] = ret[:width].to_i + ret[:height] = ret[:height].to_i + ret end module_function :resize diff --git a/lib/danbooru_image_resizer/extconf.rb b/lib/danbooru_image_resizer/extconf.rb index 5ffbbfea3..a690b9141 100644 --- a/lib/danbooru_image_resizer/extconf.rb +++ b/lib/danbooru_image_resizer/extconf.rb @@ -20,7 +20,7 @@ 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("-O2 -Wall") {true} #with_cflags("-O0 -g -fno-exceptions -Wall") {true} create_makefile("danbooru_image_resizer") diff --git a/lib/danbooru_image_resizer/test.rb b/lib/danbooru_image_resizer/test.rb new file mode 100644 index 000000000..c0d41cf66 --- /dev/null +++ b/lib/danbooru_image_resizer/test.rb @@ -0,0 +1,5 @@ +#!/usr/bin/env ruby + +require 'danbooru_image_resizer' +Danbooru.resize_image("jpg", "test.jpg", "test-out.jpg", 2490, 3500, 95) +