refactored resizer to use imagemagick, fixed some tests

This commit is contained in:
albert
2011-09-16 12:38:56 -04:00
parent fbc1fb1f51
commit a717134163
31 changed files with 69 additions and 1648 deletions

View File

@@ -28,11 +28,8 @@ gem "meta_search", :git => "git://github.com/ernie/meta_search.git"
gem "silent-postgres"
gem "whenever", :require => false
gem "sanitize", :git => "git://github.com/rgrove/sanitize.git"
gem 'rmagick'
group :development do
gem 'pry'
end
group :osx do
gem 'mini_magick'
end

View File

@@ -85,8 +85,6 @@ GEM
method_source (0.6.0)
ruby_parser (>= 2.0.5)
mime-types (1.16)
mini_magick (3.3)
subexec (~> 0.1.0)
mocha (0.10.0)
metaclass (~> 0.0.1)
multi_json (1.0.3)
@@ -128,6 +126,7 @@ GEM
thor (~> 0.14.6)
rake (0.9.2)
rdoc (3.9.4)
rmagick (2.13.1)
ruby_parser (2.2.0)
sexp_processor (~> 3.0)
sexp_processor (3.0.6)
@@ -144,7 +143,6 @@ GEM
hike (~> 1.2)
rack (~> 1.0)
tilt (!= 1.3.0, ~> 1.1)
subexec (0.1.0)
super_exception_notifier (3.0.13)
actionmailer
rake
@@ -180,12 +178,12 @@ DEPENDENCIES
mechanize
memcache-client
meta_search!
mini_magick
mocha
nokogiri
pg
pry
rails (= 3.1.0)
rmagick
sanitize!
shoulda
silent-postgres

View File

@@ -35,7 +35,7 @@ fi
# Install packages
echo "Installing packages..."
apt-get -y install build-essential automake openssl libssl-dev libyaml-dev libxml2-dev libxslt-dev autoconf ncurses-dev sudo gcc g++ libreadline-dev zlib1g-dev flex bison libgd2-noxpm libgd2-noxpm-dev bzip2 ragel memcached libmemcache-dev git curl libcurl4-openssl-dev emacs-nox postfix
apt-get -y install build-essential automake openssl libssl-dev libyaml-dev libxml2-dev libxslt-dev autoconf ncurses-dev sudo gcc g++ libreadline-dev zlib1g-dev flex bison bzip2 ragel memcached libmemcache-dev git curl libcurl4-openssl-dev emacs-nox imagemagick libmagickcore-dev libmagickwand-dev postfix
# Install PostgreSQL 9.1
apt-get -y install python-software-properties

View File

@@ -126,12 +126,12 @@ class Upload < ActiveRecord::Base
module ResizerMethods
def generate_resizes(source_path)
generate_resize_for(Danbooru.config.small_image_width, Danbooru.config.small_image_width, source_path)
generate_resize_for(Danbooru.config.small_image_width, Danbooru.config.small_image_width, source_path, 80)
generate_resize_for(Danbooru.config.medium_image_width, nil, source_path)
generate_resize_for(Danbooru.config.large_image_width, nil, source_path)
end
def generate_resize_for(width, height, source_path)
def generate_resize_for(width, height, source_path, quality = 90)
return if width.nil?
return unless image_width > width
return unless height.nil? || image_height > height
@@ -140,15 +140,7 @@ class Upload < ActiveRecord::Base
raise Error.new("file not found")
end
size = Danbooru.reduce_to({:width => image_width, :height => image_height}, {:width => width, :height => height})
# If we're not reducing the resolution, only reencode if the source image larger than
# 200 kilobytes.
if size[:width] == image_width && size[:height] == image_height && File.size?(source_path) < 200.kilobytes
return
end
Danbooru.resize(file_ext, source_path, resized_file_path_for(width), size, 90)
Danbooru.resize(source_path, resized_file_path_for(width), width, height, quality)
end
end

View File

@@ -79,11 +79,6 @@ namespace :deploy do
task :restart do
run "touch #{current_path}/tmp/restart.txt"
end
desc "Compile the image resizer"
task :compile_image_resizer do
run "cd #{release_path}/lib/danbooru_image_resizer ; ruby extconf.rb ; make"
end
end
namespace :delayed_job do
@@ -109,7 +104,6 @@ after "deploy:setup", "local_config:setup_local_files"
after "deploy:setup", "data:setup_directories"
after "deploy:symlink", "local_config:link_local_files"
after "deploy:symlink", "data:link_directories"
after "deploy:symlink", "deploy:compile_image_resizer"
after "deploy:start", "delayed_job:start"
after "deploy:stop", "delayed_job:stop"
after "deploy:restart", "delayed_job:restart"

View File

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

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

@@ -1,16 +0,0 @@
#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,60 +0,0 @@
#include <stdlib.h>
#include <string.h>
#include <gd.h>
#include "GIFReader.h"
#include "Resize.h"
bool GIF::Read(FILE *f, Filter *pOutput, char error[1024])
{
bool Ret = false;
gdImage *image = gdImageCreateFromGif(f);
if(!image)
{
strcpy(error, "couldn't read GIF");
return false;
}
uint8_t *pBuf = NULL;
pBuf = (uint8_t *) malloc(image->sx * 3);
if(pBuf == NULL)
{
strcpy(error, "out of memory");
goto cleanup;
}
pOutput->Init(image->sx, image->sy, 3);
for(int y = 0; y < image->sy; ++y)
{
uint8_t *p = pBuf;
for(int x = 0; x < image->sx; ++x)
{
int c = gdImageGetTrueColorPixel(image, x, y);
(*p++) = gdTrueColorGetRed(c);
(*p++) = gdTrueColorGetGreen(c);
(*p++) = gdTrueColorGetBlue(c);
}
if(!pOutput->WriteRow(pBuf))
{
strcpy(error, pOutput->GetError());
goto cleanup;
}
}
if(!pOutput->Finish())
{
strcpy(error, pOutput->GetError());
goto cleanup;
}
Ret = true;
cleanup:
if(pBuf != NULL)
free(pBuf);
gdImageDestroy(image);
return Ret;
}

View File

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

View File

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

@@ -1,29 +0,0 @@
#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,180 +0,0 @@
#include <string.h>
#include <assert.h>
#include "JPEGReader.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;
}
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;
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_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);
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, Filter *pOutput, char error[1024])
{
// JMSG_LENGTH_MAX <= sizeof(error)
m_pOutputFilter = pOutput;
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;
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(!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;
}
while(CInfo.output_scanline < CInfo.output_height)
{
jpeg_read_scanlines(&CInfo, &pBuf, 1);
if(!m_pOutputFilter->WriteRow(pBuf))
{
strcpy(error, m_pOutputFilter->GetError());
goto cleanup;
}
}
if(!m_pOutputFilter->Finish())
{
strcpy(error, m_pOutputFilter->GetError());
goto cleanup;
}
jpeg_finish_decompress(&CInfo);
Ret = true;
cleanup:
if(pBuf != NULL)
free(pBuf);
jpeg_destroy_decompress(&CInfo);
return Ret;
}

View File

@@ -1,50 +0,0 @@
#ifndef JPEG_READER_H
#define JPEG_READER_H
#include <stdio.h>
#include <stdint.h>
#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[JMSG_LENGTH_MAX];
};
class JPEG: public Reader
{
public:
bool Read(FILE *f, Filter *pOutput, char error[1024]);
private:
Filter *m_pOutputFilter;
struct jpeg_error m_JErr;
};
class JPEGCompressor: public Filter
{
public:
JPEGCompressor(FILE *f);
~JPEGCompressor();
bool Init(int iSourceWidth, int iSourceHeight, int iBPP);
void SetQuality(int quality);
bool WriteRow(uint8_t *row);
bool Finish();
int GetWidth() const;
int GetHeight() const;
const char *GetError() const;
private:
FILE *m_File;
int m_iQuality;
struct jpeg_compress_struct m_CInfo;
struct jpeg_error m_JErr;
};
#endif

View File

@@ -1,187 +0,0 @@
SHELL = /bin/sh
#### Start of system configuration section. ####
srcdir = .
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-p0
rubylibprefix = $(libdir)/$(RUBY_BASE_NAME)
exec_prefix = $(prefix)
vendorhdrdir = $(rubyhdrdir)/vendor_ruby
sitehdrdir = $(rubyhdrdir)/site_ruby
rubyhdrdir = $(includedir)/$(RUBY_BASE_NAME)-$(ruby_version)
vendordir = $(rubylibprefix)/vendor_ruby
sitedir = $(rubylibprefix)/site_ruby
ridir = $(datarootdir)/$(RI_BASE_NAME)
mandir = $(datarootdir)/man
localedir = $(datarootdir)/locale
libdir = $(exec_prefix)/lib
psdir = $(docdir)
pdfdir = $(docdir)
dvidir = $(docdir)
htmldir = $(docdir)
infodir = $(datarootdir)/info
docdir = $(datarootdir)/doc/$(PACKAGE)
oldincludedir = $(DESTDIR)/usr/include
includedir = $(prefix)/include
localstatedir = $(prefix)/var
sharedstatedir = $(prefix)/com
sysconfdir = $(prefix)/etc
datadir = $(datarootdir)
datarootdir = $(prefix)/share
libexecdir = $(exec_prefix)/libexec
sbindir = $(exec_prefix)/sbin
bindir = $(exec_prefix)/bin
rubylibdir = $(rubylibprefix)/$(ruby_version)
archdir = $(rubylibdir)/$(arch)
sitelibdir = $(sitedir)/$(ruby_version)
sitearchdir = $(sitelibdir)/$(sitearch)
vendorlibdir = $(vendordir)/$(ruby_version)
vendorarchdir = $(vendorlibdir)/$(sitearch)
CC = g++
CXX = 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
OUTFLAG = -o
COUTFLAG = -o
RUBY_EXTCONF_H =
cflags = $(optflags) $(debugflags) $(warnflags)
optflags = -O3
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 = -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.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-p0/bin/ruby
RUBY = $(ruby)
RM = rm -f
RM_RF = $(RUBY) -run -e rm -- -rf
RMDIRS = $(RUBY) -run -e rmdir -- -p
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) /opt/local/lib
LIBPATH = -L. -L$(libdir) -L/opt/local/lib
DEFFILE =
CLEANFILES = mkmf.log
DISTCLEANFILES =
DISTCLEANDIRS =
extout =
extout_prefix =
target_prefix =
LOCAL_LIBS =
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 =
STATIC_LIB =
BINDIR = $(bindir)
RUBYCOMMONDIR = $(sitedir)$(target_prefix)
RUBYLIBDIR = $(sitelibdir)$(target_prefix)
RUBYARCHDIR = $(sitearchdir)$(target_prefix)
HDRDIR = $(rubyhdrdir)/ruby$(target_prefix)
ARCHHDRDIR = $(rubyhdrdir)/$(arch)/ruby$(target_prefix)
TARGET_SO = $(DLLIB)
CLEANLIBS = $(TARGET).bundle
CLEANOBJS = *.o *.bak
all: $(DLLIB)
static: $(STATIC_LIB)
.PHONY: all install static install-so install-rb
.PHONY: clean clean-so clean-rb
clean-rb-default::
clean-rb::
clean-so::
clean: clean-so clean-rb-default clean-rb
@-$(RM) $(CLEANLIBS) $(CLEANOBJS) $(CLEANFILES)
distclean-rb-default::
distclean-rb::
distclean-so::
distclean: clean distclean-so distclean-rb-default distclean-rb
@-$(RM) Makefile $(RUBY_EXTCONF_H) conftest.* mkmf.log
@-$(RM) core ruby$(EXEEXT) *~ $(DISTCLEANFILES)
@-$(RMDIRS) $(DISTCLEANDIRS)
realclean: distclean
install: install-so install-rb
install-so: $(RUBYARCHDIR)
install-so: $(RUBYARCHDIR)/$(DLLIB)
$(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
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) $(COUTFLAG)$@ -c $<
.cxx.o:
$(CXX) $(INCFLAGS) $(CPPFLAGS) $(CXXFLAGS) $(COUTFLAG)$@ -c $<
.cpp.o:
$(CXX) $(INCFLAGS) $(CPPFLAGS) $(CXXFLAGS) $(COUTFLAG)$@ -c $<
.C.o:
$(CXX) $(INCFLAGS) $(CPPFLAGS) $(CXXFLAGS) $(COUTFLAG)$@ -c $<
.c.o:
$(CC) $(INCFLAGS) $(CPPFLAGS) $(CFLAGS) $(COUTFLAG)$@ -c $<
$(DLLIB): $(OBJS) Makefile
@-$(RM) $(@)
$(LDSHAREDXX) -o $@ $(OBJS) $(LIBPATH) $(DLDFLAGS) $(LOCAL_LIBS) $(LIBS)
$(OBJS): $(hdrdir)/ruby.h $(hdrdir)/ruby/defines.h $(arch_hdrdir)/ruby/config.h

View File

@@ -1,139 +0,0 @@
#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_pOutputFilter->Init(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. */
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(row_num+1);
}
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, Filter *pOutput, char error[1024])
{
m_pOutputFilter = pOutput;
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_pOutputFilter->Finish())
Error(png, m_pOutputFilter->GetError());
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

@@ -1,38 +0,0 @@
#ifndef PNG_READER_H
#define PNG_READER_H
#include <png.h>
#include "Reader.h"
#include "Filter.h"
#include "RowBuffer.h"
struct png_error_info
{
char *err;
};
class PNG: public Reader
{
public:
PNG()
{
m_Done = false;
}
bool Read(FILE *f, Filter *pOutput, char error[1024]);
private:
RowBuffer<uint8_t> m_Rows;
Filter *m_pOutputFilter;
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

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

View File

@@ -1,286 +0,0 @@
#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include "Resize.h"
#include "Filter.h"
#include <algorithm>
using namespace std;
namespace
{
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()
{
if(m_OutBuf)
free(m_OutBuf);
}
const char *Resizer::GetError() const
{
if(m_szError != NULL)
return m_szError;
return m_pCompressor->GetError();
}
bool Resizer::Init(int iSourceWidth, int iSourceHeight, int iBPP)
{
assert(m_DestWidth != -1);
assert(m_DestHeight != -1);
assert(iBPP == 3);
m_SourceWidth = iSourceWidth;
m_SourceHeight = iSourceHeight;
m_SourceBPP = iBPP;
float fXFactor = float(m_SourceWidth) / m_DestWidth;
m_XFilter.Init(fXFactor);
float fYFactor = float(m_SourceHeight) / m_DestHeight;
m_YFilter.Init(fYFactor);
if(!m_Rows.Init(m_DestWidth, m_SourceHeight, m_SourceBPP, m_YFilter.m_iTaps))
{
m_szError = "out of memory";
return false;
}
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);
}
void Resizer::SetDest(int iDestWidth, int iDestHeight)
{
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)
{
for(int i = 0; i < iBPP; ++i)
pDest[i] = pSourceRow[i];
pDest += iBPP;
}
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;
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)
{
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);
const uint8_t *pSource = pPaddedRow + iStartX*3;
float fR = 0, fG = 0, fB = 0;
for(int i = 0; i < m_XFilter.m_iTaps; ++i)
{
float fWeight = *pFilter++;
fR += pSource[0] * fWeight;
fG += pSource[1] * fWeight;
fB += pSource[2] * fWeight;
pSource += 3;
}
pOutput[0] = fR;
pOutput[1] = fG;
pOutput[2] = fB;
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_pCompressor->WriteRow((uint8_t *) m_OutBuf))
return false;
++m_CurrentY;
}
if(m_CurrentY == m_DestHeight)
{
if(!m_pCompressor->Finish())
return false;
}
return true;
}

View File

@@ -1,56 +0,0 @@
#ifndef RESIZE_H
#define RESIZE_H
#include "RowBuffer.h"
#include "Filter.h"
#include <memory>
using namespace std;
#include <stdint.h>
struct LanczosFilter
{
LanczosFilter();
~LanczosFilter();
void Init(float fFactor);
const float *GetFilter(float fOffset) const;
float m_fStep;
int m_iTaps;
float *m_pFilters;
};
class Resizer: public Filter
{
public:
Resizer(auto_ptr<Filter> pCompressor);
~Resizer();
// BPP is 3 or 4, indicating RGB or RGBA.
bool Init(int iSourceWidth, int iSourceHeight, int BPP);
void SetDest(int iDestWidth, int iDestHeight);
bool WriteRow(uint8_t *pNewRow);
bool Finish() { return true; }
const char *GetError() const;
private:
auto_ptr<Filter> m_pCompressor;
uint8_t *m_OutBuf;
RowBuffer<float> m_Rows;
const char *m_szError;
int m_SourceWidth;
int m_SourceHeight;
int m_SourceBPP;
int m_DestWidth;
int m_DestHeight;
LanczosFilter m_XFilter;
LanczosFilter m_YFilter;
int m_iInputY;
int m_CurrentY;
};
#endif

View File

@@ -1,137 +0,0 @@
#ifndef ROW_BUFFER_H
#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()
{
m_Rows = NULL;
m_ActualRows = NULL;
m_StartRow = 0;
m_EndRow = 0;
m_BPP = 0;
m_Height = 0;
}
~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. */
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)
{
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 T *const *GetRows() const { return m_Rows; }
private:
/* Array of image rows. These are allocated as needed. */
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;
int m_EndRow;
int m_Width;
int m_Height;
int m_BPP;
int m_iVertPadding;
};
#endif

View File

@@ -1,161 +0,0 @@
#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 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);
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];
try
{
auto_ptr<Reader> pReader(GetReader(file_ext));
if(pReader.get() == NULL)
{
strcpy(error, "unknown filetype");
goto cleanup;
}
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");
}
cleanup:
fclose(read_file);
fclose(write_file);
if(!ret)
rb_raise(rb_eException, "%s", error);
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

@@ -1,34 +1,49 @@
begin
require 'danbooru_image_resizer/danbooru_image_resizer.so'
rescue LoadError
require 'danbooru_image_resizer/danbooru_image_resizer_fallback'
end
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, ratio = 1)
ret = size.dup
def resize(read_path, write_path, width, height, resize_quality = 90)
image = Magick::Image.read(read_path).first
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
if width == Danbooru.config.small_image_width
image.change_geometry("#{width}x#{height}>") do |small_width, small_height, img|
img.thumbnail!(small_width, small_height)
width = small_width
height = small_height
end
else
image.change_geometry("#{width}x>") do |new_width, new_height, img|
img.resize!(new_width, new_height)
width = new_width
height = new_height
end
end
ret[:width] = ret[:width].to_i
ret[:height] = ret[:height].to_i
ret
image = flatten(image, width, height)
image.write(write_path) do
self.quality = resize_quality
end
image.destroy!
end
def flatten(image, width, height)
if image.alpha?
# since jpeg can't represent transparency, we need to create an image list,
# put a white image on the bottom, then flatten it.
list = Magick::ImageList.new
list.new_image(width, height) do
self.background_color = "#FFFFFF"
end
list << image
flattened_image = list.flatten_images
list.each do |image|
image.destroy!
end
return flattened_image
else
return image
end
end
module_function :resize
module_function :reduce_to
module_function :resize, :flatten
end

View File

@@ -1,14 +0,0 @@
module Danbooru
def resize_image(extension, original_path, destination_path, width, height, quality)
require 'mini_magick'
image = MiniMagick::Image.open(original_path)
image.resize "#{width}x#{height}"
image.format extension
image.write destination_path
end
def is_fallback
end
module_function :resize_image, :is_fallback
end

View File

@@ -1,26 +0,0 @@
#!/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 -Wall") {true}
#with_cflags("-O0 -g -fno-exceptions -Wall") {true}
create_makefile("danbooru_image_resizer")

View File

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

View File

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

View File

@@ -38,4 +38,17 @@ class DownloadTest < ActiveSupport::TestCase
assert_match(/image\/gif/, @download.content_type)
end
end
context "a post download for a pixiv manga page" do
setup do
@source = "http://img65.pixiv.net/img/kiyoringo/21755794_p2.png"
@tempfile = Tempfile.new("danbooru-test")
@download = Download.new(@source, @tempfile.path)
end
should "download the big version" do
@download.download!
assert_equal("http://img65.pixiv.net/img/kiyoringo/21755794_big_p2.png", @download.source)
end
end
end

View File

@@ -60,7 +60,7 @@ class NoteTest < ActiveSupport::TestCase
assert_nil(@post.last_noted_at)
@note.update_attributes(:x => 1000)
@post.reload
assert_equal(@post.last_noted_at, @note.updated_at)
assert_equal(@post.last_noted_at.to_i, @note.updated_at.to_i)
end
should "create a version" do

View File

@@ -13,16 +13,5 @@ class PixivProxyTest < ActiveSupport::TestCase
assert(first_tag[0] =~ /./)
assert(first_tag[1] =~ /tags\.php\?tag=/)
end
should "get a manga page" do
url ="http://img65.pixiv.net/img/kiyoringo/21755794_p2.png"
results = PixivProxy.get_single(url)
assert_equal("member.php?id=4015", results[:profile_url])
assert(results[:jp_tags].size > 0)
first_tag = results[:jp_tags][0]
assert_equal(2, first_tag.size)
assert(first_tag[0] =~ /./)
assert(first_tag[1] =~ /tags\.php\?tag=/)
end
end
end

View File

@@ -110,22 +110,12 @@ class UploadTest < ActiveSupport::TestCase
@upload.calculate_hash(@upload.file_path)
@upload.calculate_dimensions(@upload.file_path)
assert_nothing_raised {@upload.generate_resizes(@upload.file_path)}
unless Danbooru.respond_to? :is_fallback
assert(File.exists?(@upload.resized_file_path_for(Danbooru.config.small_image_width)))
assert_equal(7265, File.size(@upload.resized_file_path_for(Danbooru.config.small_image_width)))
assert(File.exists?(@upload.resized_file_path_for(Danbooru.config.medium_image_width)))
assert_equal(43474, File.size(@upload.resized_file_path_for(Danbooru.config.medium_image_width)))
assert(File.exists?(@upload.resized_file_path_for(Danbooru.config.large_image_width)))
assert_equal(198583, File.size(@upload.resized_file_path_for(Danbooru.config.large_image_width)))
else
# Different set of expected file sizes due to using a different compression algorithm
assert(File.exists?(@upload.resized_file_path_for(Danbooru.config.small_image_width)))
assert_equal(7405, File.size(@upload.resized_file_path_for(Danbooru.config.small_image_width)))
assert(File.exists?(@upload.resized_file_path_for(Danbooru.config.medium_image_width)))
assert_equal(43577, File.size(@upload.resized_file_path_for(Danbooru.config.medium_image_width)))
assert(File.exists?(@upload.resized_file_path_for(Danbooru.config.large_image_width)))
assert_equal(198666, File.size(@upload.resized_file_path_for(Danbooru.config.large_image_width)))
end
assert(File.exists?(@upload.resized_file_path_for(Danbooru.config.small_image_width)))
assert_equal(4817, File.size(@upload.resized_file_path_for(Danbooru.config.small_image_width)))
assert(File.exists?(@upload.resized_file_path_for(Danbooru.config.medium_image_width)))
assert_equal(42990, File.size(@upload.resized_file_path_for(Danbooru.config.medium_image_width)))
assert(File.exists?(@upload.resized_file_path_for(Danbooru.config.large_image_width)))
assert_equal(197046, File.size(@upload.resized_file_path_for(Danbooru.config.large_image_width)))
end
end