updated resizer

This commit is contained in:
albert
2011-08-22 17:46:20 -04:00
parent 6b01c88679
commit 97134c3268
21 changed files with 792 additions and 356 deletions

View File

@@ -0,0 +1,66 @@
#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include "ConvertToRGB.h"
#include "Filter.h"
#include <algorithm>
using namespace std;
ConvertToRGB::ConvertToRGB(auto_ptr<Filter> 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);
}

View File

@@ -0,0 +1,27 @@
#ifndef CONVERT_TO_RGB_H
#define CONVERT_TO_RGB_H
#include "Filter.h"
#include <memory>
using namespace std;
class ConvertToRGB: public Filter
{
public:
ConvertToRGB(auto_ptr<Filter> 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<Filter> m_pCompressor;
int m_iSourceWidth;
int m_iBPP;
};
#endif

View File

@@ -0,0 +1,16 @@
#ifndef FILTER_H
#define FILTER_H
#include <stdint.h>
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

View File

@@ -1,12 +1,11 @@
#include <stdlib.h>
#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])
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;
}

View File

@@ -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

View File

@@ -0,0 +1,48 @@
#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include "Histogram.h"
#include "Filter.h"
#include <algorithm>
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;
}

View File

@@ -0,0 +1,29 @@
#ifndef HISTOGRAM_H
#define HISTOGRAM_H
#include "Filter.h"
#include <memory>
using namespace std;
#include <stdint.h>
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

View File

@@ -1,6 +1,6 @@
#include <string.h>
#include <assert.h>
#include "JPEGReader.h"
#include "RowBuffer.h"
#include "Resize.h"
#include <algorithm>
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;

View File

@@ -6,31 +6,33 @@
#include <setjmp.h>
#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;
};

View File

@@ -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

View File

@@ -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");

View File

@@ -3,6 +3,7 @@
#include <png.h>
#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<uint8_t> m_Rows;
Filter *m_pOutputFilter;
bool m_Done;
int m_Passes;

View File

@@ -3,12 +3,12 @@
#include <stdio.h>
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

View File

@@ -1,17 +1,101 @@
#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include "Resize.h"
#include "JPEGReader.h"
#include "Filter.h"
#include <algorithm>
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<m_iTaps; ++i)
pOutput[i] /= fSum;
pOutput += m_iTaps;
}
}
const float *LanczosFilter::GetFilter(float fOffset) const
{
int iOffset = int(fOffset * 256.0f);
iOffset %= 256;
return m_pFilters + iOffset*m_iTaps;
}
Resizer::Resizer(auto_ptr<Filter> 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;
}

View File

@@ -1,32 +1,43 @@
#ifndef RESIZE_H
#define RESIZE_H
#include "RowBuffer.h"
#include "Filter.h"
#include <memory>
using namespace std;
#include <stdint.h>
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<Filter> 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<Filter> m_pCompressor;
uint8_t *m_OutBuf;
RowBuffer<float> 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

View File

@@ -1,81 +0,0 @@
#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

@@ -2,30 +2,126 @@
#define ROW_BUFFER_H
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include <assert.h>
#include "RowBuffer.h"
#include <algorithm>
using namespace std;
template<typename T>
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

View File

@@ -1,15 +1,31 @@
#include <ruby.h>
#include <stdio.h>
#include <string.h>
#include <memory>
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<Reader> pReader(GetReader(file_ext));
if(pReader.get() == NULL)
{
strcpy(error, "unknown filetype");
goto cleanup;
}
Compressor = new JPEGCompressor(write_file);
if(Compressor == NULL)
auto_ptr<Filter> pFilter(NULL);
{
auto_ptr<JPEGCompressor> pCompressor(new JPEGCompressor(write_file));
pCompressor->SetQuality(output_quality);
pFilter.reset(pCompressor.release());
}
{
auto_ptr<Resizer> pResizer(new Resizer(pFilter));
pResizer->SetDest(output_width, output_height);
pFilter.reset(pResizer.release());
}
{
auto_ptr<ConvertToRGB> 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<Reader> pReader(GetReader(file_ext));
if(pReader.get() == NULL)
{
strcpy(error, "unknown filetype");
goto cleanup;
}
auto_ptr<Filter> pFilter(NULL);
Histogram *pHistogram = new Histogram();
pFilter.reset(pHistogram);
{
auto_ptr<ConvertToRGB> 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);
}

View File

@@ -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

View File

@@ -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")

View File

@@ -0,0 +1,5 @@
#!/usr/bin/env ruby
require 'danbooru_image_resizer'
Danbooru.resize_image("jpg", "test.jpg", "test-out.jpg", 2490, 3500, 95)