Skip to content

Instantly share code, notes, and snippets.

@ucnv
Last active May 4, 2020 10:58
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save ucnv/708485 to your computer and use it in GitHub Desktop.
Save ucnv/708485 to your computer and use it in GitHub Desktop.
# This file is a kind of Ruby port of JPGEncoder.as
# <https://github.com/mikechambers/as3corelib/blob/master/src/com/adobe/images/JPGEncoder.as>
# Copyright (c) 2008, Adobe Systems Incorporated
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are
# met:
#
# * Redistributions of source code must retain the above copyright notice,
# this list of conditions and the following disclaimer.
#
# * Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
#
# * Neither the name of Adobe Systems Incorporated nor the names of its
# contributors may be used to endorse or promote products derived from
# this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
# THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
require 'rmagick'
class JPGEncoder
def initialize quality = 80
init_values
init_huffman_tbl
init_category_number
init_RGB_YUV_table
self.quality quality
end
def init_values
@byteout = []
@bytenew = 0
@bytepos = 7
@YTable = Array.new 64
@UVTable = Array.new 64
@fdtbl_Y = Array.new 64
@fdtbl_UV = Array.new 64
@YDC_HT
@UVDC_HT
@YAC_HT
@UVAC_HT
@bitcode = Array.new 65535
@category = Array.new 65535
@output_fDCTQ = Array.new 64
@DU = Array.new 64
@YDU = Array.new 64
@UDU = Array.new 64
@VDU = Array.new 64
@RGB_YUV_TABLE = Array.new 2048
@zigzag = [
0, 1, 5, 6,14,15,27,28,
2, 4, 7,13,16,26,29,42,
3, 8,12,17,25,30,41,43,
9,11,18,24,31,40,44,53,
10,19,23,32,39,45,52,54,
20,22,33,38,46,51,55,60,
21,34,37,47,50,56,59,61,
35,36,48,49,57,58,62,63
]
@std_dc_luminance_nrcodes = [0,0,1,5,1,1,1,1,1,1,0,0,0,0,0,0,0]
@std_dc_luminance_values = [0,1,2,3,4,5,6,7,8,9,10,11]
@std_ac_luminance_nrcodes = [0,0,2,1,3,3,2,4,3,5,5,4,4,0,0,1,0x7d]
@std_ac_luminance_values = [
0x01,0x02,0x03,0x00,0x04,0x11,0x05,0x12,
0x21,0x31,0x41,0x06,0x13,0x51,0x61,0x07,
0x22,0x71,0x14,0x32,0x81,0x91,0xa1,0x08,
0x23,0x42,0xb1,0xc1,0x15,0x52,0xd1,0xf0,
0x24,0x33,0x62,0x72,0x82,0x09,0x0a,0x16,
0x17,0x18,0x19,0x1a,0x25,0x26,0x27,0x28,
0x29,0x2a,0x34,0x35,0x36,0x37,0x38,0x39,
0x3a,0x43,0x44,0x45,0x46,0x47,0x48,0x49,
0x4a,0x53,0x54,0x55,0x56,0x57,0x58,0x59,
0x5a,0x63,0x64,0x65,0x66,0x67,0x68,0x69,
0x6a,0x73,0x74,0x75,0x76,0x77,0x78,0x79,
0x7a,0x83,0x84,0x85,0x86,0x87,0x88,0x89,
0x8a,0x92,0x93,0x94,0x95,0x96,0x97,0x98,
0x99,0x9a,0xa2,0xa3,0xa4,0xa5,0xa6,0xa7,
0xa8,0xa9,0xaa,0xb2,0xb3,0xb4,0xb5,0xb6,
0xb7,0xb8,0xb9,0xba,0xc2,0xc3,0xc4,0xc5,
0xc6,0xc7,0xc8,0xc9,0xca,0xd2,0xd3,0xd4,
0xd5,0xd6,0xd7,0xd8,0xd9,0xda,0xe1,0xe2,
0xe3,0xe4,0xe5,0xe6,0xe7,0xe8,0xe9,0xea,
0xf1,0xf2,0xf3,0xf4,0xf5,0xf6,0xf7,0xf8,
0xf9,0xfa
]
@std_dc_chrominance_nrcodes = [0,0,3,1,1,1,1,1,1,1,1,1,0,0,0,0,0]
@std_dc_chrominance_values = [0,1,2,3,4,5,6,7,8,9,10,11]
@std_ac_chrominance_nrcodes = [0,0,2,1,2,4,4,3,4,7,5,4,4,0,1,2,0x77]
@std_ac_chrominance_values = [
0x00,0x01,0x02,0x03,0x11,0x04,0x05,0x21,
0x31,0x06,0x12,0x41,0x51,0x07,0x61,0x71,
0x13,0x22,0x32,0x81,0x08,0x14,0x42,0x91,
0xa1,0xb1,0xc1,0x09,0x23,0x33,0x52,0xf0,
0x15,0x62,0x72,0xd1,0x0a,0x16,0x24,0x34,
0xe1,0x25,0xf1,0x17,0x18,0x19,0x1a,0x26,
0x27,0x28,0x29,0x2a,0x35,0x36,0x37,0x38,
0x39,0x3a,0x43,0x44,0x45,0x46,0x47,0x48,
0x49,0x4a,0x53,0x54,0x55,0x56,0x57,0x58,
0x59,0x5a,0x63,0x64,0x65,0x66,0x67,0x68,
0x69,0x6a,0x73,0x74,0x75,0x76,0x77,0x78,
0x79,0x7a,0x82,0x83,0x84,0x85,0x86,0x87,
0x88,0x89,0x8a,0x92,0x93,0x94,0x95,0x96,
0x97,0x98,0x99,0x9a,0xa2,0xa3,0xa4,0xa5,
0xa6,0xa7,0xa8,0xa9,0xaa,0xb2,0xb3,0xb4,
0xb5,0xb6,0xb7,0xb8,0xb9,0xba,0xc2,0xc3,
0xc4,0xc5,0xc6,0xc7,0xc8,0xc9,0xca,0xd2,
0xd3,0xd4,0xd5,0xd6,0xd7,0xd8,0xd9,0xda,
0xe2,0xe3,0xe4,0xe5,0xe6,0xe7,0xe8,0xe9,
0xea,0xf2,0xf3,0xf4,0xf5,0xf6,0xf7,0xf8,
0xf9,0xfa
]
end
def quality value = nil
return @current_quality if value.nil?
value = 1 if value <= 0
value = 100 if value > 100
return if @current_quality == value
sf = 0
if value < 50
sf = (5000 / value).floor
else
sf = (200 - value * 2).floor
end
init_quant_tables sf
@current_quality = value
end
def encode filename
img = Magick::ImageList.new(filename).first
width = img.columns
height = img.rows
image_data = img.get_pixels 0, 0, width, height
@byteout = []
@bytenew = 0
@bytepos = 7
# header
write_word 0xFFD8 # SOI
write_APP0
write_DQT
write_SOF0 width, height
write_DHT
write_SOS
vDCY = 0
vDCU = 0
vDCV = 0
@bytenew = 0
@bytepos = 7
quad_width = width * 4
triple_width = width * 3
x, y = 0, 0
r, b, g = ()
start, p, col, row, pos = ()
t = @RGB_YUV_TABLE
while y < height do
x = 0
while x < quad_width do
start = quad_width * y + x - 1
p = start
col = -1
row = 0
64.times do |pos|
row = pos >> 3 # /8
col = (pos & 7) * 4 # %8
p = start + (row * quad_width) + col
if y + row >= height # padding bottom
p -= quad_width * (y + 1 + row - height)
end
if x + col > quad_width # padding right
p -= (x + col) - quad_width + 4
end
pixel = image_data[p / 4]
p += 3
r = (pixel.red / 256).to_i
g = (pixel.green / 256).to_i
b = (pixel.blue / 256).to_i
@YDU[pos] = ((t[r] + t[(g + 256) >> 0] + t[(b + 512)>>0]) >> 16) - 128
@UDU[pos] = ((t[(r + 768) >> 0] + t[(g + 1024) >> 0] + t[(b + 1280)>>0]) >> 16) - 128
@VDU[pos] = ((t[(r + 1280) >> 0] + t[(g + 1536) >> 0] + t[(b + 1792)>>0]) >> 16) - 128
end
vDCY = process_DU @YDU, @fdtbl_Y, vDCY, @YDC_HT, @YAC_HT
vDCU = process_DU @UDU, @fdtbl_UV, vDCU, @UVDC_HT, @UVAC_HT
vDCV = process_DU @VDU, @fdtbl_UV, vDCV, @UVDC_HT, @UVAC_HT
x += 32
end
y += 8
end
# byte align
if @bytepos >= 0
fillbits = []
fillbits[1] = @bytepos + 1
fillbits[0] = (1 << (@bytepos + 1)) - 1
write_bits fillbits
end
write_word 0xFFD9 # EOI
self
end
def output filename
File.open(filename, 'w') do |f|
x = @byteout.join
f.print x
end
@byteout = []
end
def process_DU cdu, fdtbl, dc, htdc, htac
eob = htac[0x00]
m16zeroes = htac[0xF0]
pos = 0
vDU_DCT = f_DCT_quant(cdu, fdtbl)
64.times do |i|
@DU[@zigzag[i]] = vDU_DCT[i]
end
diff = @DU[0] - dc
dc = @DU[0]
if diff == 0
write_bits htdc[0]
else
pos = 32767 + diff
write_bits htdc[@category[pos]]
write_bits @bitcode[pos]
end
end0pos = 63
while end0pos > 0 && @DU[end0pos] == 0 do
end0pos -= 1
end
if end0pos == 0
write_bits eob
return dc
end
i = 1
lng = 0
while i <= end0pos do
startpos = i
while (@DU[i] == 0) && (i <= end0pos) do
i += 1
end
nrzeroes = i - startpos
if nrzeroes >= 16
lng = nrzeroes >> 4
lng.times do
write_bits m16zeroes
end
nrzeroes = nrzeroes & 0xF
end
pos = 32767 + @DU[i]
write_bits htac[(nrzeroes << 4) + @category[pos]]
write_bits @bitcode[pos]
i += 1
end
if end0pos != 63
write_bits eob
end
dc
end
def init_quant_tables sf
yqt = [
16, 11, 10, 16, 24, 40, 51, 61,
12, 12, 14, 19, 26, 58, 60, 55,
14, 13, 16, 24, 40, 57, 69, 56,
14, 17, 22, 29, 51, 87, 80, 62,
18, 22, 37, 56, 68,109,103, 77,
24, 35, 55, 64, 81,104,113, 92,
49, 64, 78, 87,103,121,120,101,
72, 92, 95, 98,112,100,103, 99
]
64.times do |i|
t = ((yqt[i] * sf + 50) / 100).floor
t = 1 if t < 1
t = 255 if t > 255
@YTable[@zigzag[i]] = t
end
uvqt = [
17, 18, 24, 47, 99, 99, 99, 99,
18, 21, 26, 66, 99, 99, 99, 99,
24, 26, 56, 99, 99, 99, 99, 99,
47, 66, 99, 99, 99, 99, 99, 99,
99, 99, 99, 99, 99, 99, 99, 99,
99, 99, 99, 99, 99, 99, 99, 99,
99, 99, 99, 99, 99, 99, 99, 99,
99, 99, 99, 99, 99, 99, 99, 99
]
64.times do |i|
u = ((uvqt[i] * sf + 50) / 100).floor
u = 1 if u < 1
u = 255 if u > 255
@UVTable[@zigzag[i]] = u
end
aasf = [
1.0, 1.387039845, 1.306562965, 1.175875602,
1.0, 0.785694958, 0.541196100, 0.275899379
]
k = 0
8.times do |row|
8.times do |col|
@fdtbl_Y[k] = (1.0 / (@YTable [@zigzag[k]] * aasf[row] * aasf[col] * 8.0))
@fdtbl_UV[k] = (1.0 / (@UVTable[@zigzag[k]] * aasf[row] * aasf[col] * 8.0))
k += 1
end
end
end
def init_huffman_tbl
@YDC_HT = compute_huffman_tbl @std_dc_luminance_nrcodes, @std_dc_luminance_values
@UVDC_HT = compute_huffman_tbl @std_dc_chrominance_nrcodes, @std_dc_chrominance_values
@YAC_HT = compute_huffman_tbl @std_ac_luminance_nrcodes, @std_ac_luminance_values
@UVAC_HT = compute_huffman_tbl @std_ac_chrominance_nrcodes, @std_ac_chrominance_values
end
def compute_huffman_tbl nrcodes, std_table
codevalue = 0
pos_in_table = 0
ht = []
16.times do |i|
k = i + 1
nrcodes[k].times do |j|
ht[std_table[pos_in_table]] = []
ht[std_table[pos_in_table]][0] = codevalue
ht[std_table[pos_in_table]][1] = k
pos_in_table += 1
codevalue += 1
end
codevalue *= 2
end
ht
end
def init_category_number
nrlower = 1
nrupper = 2
15.times do |i|
cat = i + 1
# Positive numbers
(nrlower..(nrupper - 1)).each do |nr|
@category[32767 + nr] = cat
@bitcode[32767 + nr] = []
@bitcode[32767 + nr][1] = cat
@bitcode[32767 + nr][0] = nr
end
# Negative numbers
((-(nrupper-1))..(-nrlower)).each do |nrneg|
@category[32767 + nrneg] = cat
@bitcode[32767 + nrneg] = []
@bitcode[32767 + nrneg][1] = cat
@bitcode[32767 + nrneg][0] = nrupper - 1 + nrneg
end
nrlower = nrlower << 1
nrupper = nrupper << 1
end
end
def init_RGB_YUV_table
256.times do |i|
@RGB_YUV_TABLE[i] = 19595 * i
@RGB_YUV_TABLE[(i+ 256)>>0] = 38470 * i
@RGB_YUV_TABLE[(i+ 512)>>0] = 7471 * i + 0x8000
@RGB_YUV_TABLE[(i+ 768)>>0] = -11059 * i
@RGB_YUV_TABLE[(i+1024)>>0] = -21709 * i
@RGB_YUV_TABLE[(i+1280)>>0] = 32768 * i + 0x807FFF
@RGB_YUV_TABLE[(i+1536)>>0] = -27439 * i
@RGB_YUV_TABLE[(i+1792)>>0] = - 5329 * i
end
end
def f_DCT_quant data, fdtbl
d0, d1, d2, d3, d4, d5, d6, d7 = ()
off = 0
8.times do |i|
d0 = data[off]
d1 = data[off + 1]
d2 = data[off + 2]
d3 = data[off + 3]
d4 = data[off + 4]
d5 = data[off + 5]
d6 = data[off + 6]
d7 = data[off + 7]
tmp0 = d0 + d7
tmp7 = d0 - d7
tmp1 = d1 + d6
tmp6 = d1 - d6
tmp2 = d2 + d5
tmp5 = d2 - d5
tmp3 = d3 + d4
tmp4 = d3 - d4
tmp10 = tmp0 + tmp3
tmp13 = tmp0 - tmp3
tmp11 = tmp1 + tmp2
tmp12 = tmp1 - tmp2
data[off] = tmp10 + tmp11
data[off + 4] = tmp10 - tmp11
z1 = (tmp12 + tmp13) * 0.707106781
data[off + 2] = tmp13 + z1
data[off + 6] = tmp13 - z1
tmp10 = tmp4 + tmp5
tmp11 = tmp5 + tmp6
tmp12 = tmp6 + tmp7
z5 = (tmp10 - tmp12) * 0.382683433
z2 = 0.541196100 * tmp10 + z5
z4 = 1.306562965 * tmp12 + z5
z3 = tmp11 * 0.707106781
z11 = tmp7 + z3
z13 = tmp7 - z3
data[off + 5] = z13 + z2
data[off + 3] = z13 - z2
data[off + 1] = z11 + z4
data[off + 7] = z11 - z4
off += 8
end
off = 0
8.times do |i|
d0 = data[off]
d1 = data[off + 8]
d2 = data[off + 16]
d3 = data[off + 24]
d4 = data[off + 32]
d5 = data[off + 40]
d6 = data[off + 48]
d7 = data[off + 56]
tmp0p2 = d0 + d7
tmp7p2 = d0 - d7
tmp1p2 = d1 + d6
tmp6p2 = d1 - d6
tmp2p2 = d2 + d5
tmp5p2 = d2 - d5
tmp3p2 = d3 + d4
tmp4p2 = d3 - d4
tmp10p2 = tmp0p2 + tmp3p2
tmp13p2 = tmp0p2 - tmp3p2
tmp11p2 = tmp1p2 + tmp2p2
tmp12p2 = tmp1p2 - tmp2p2
data[off] = tmp10p2 + tmp11p2
data[off + 32] = tmp10p2 - tmp11p2
z1p2 = (tmp12p2 + tmp13p2) * 0.707106781
data[off + 16] = tmp13p2 + z1p2
data[off + 48] = tmp13p2 - z1p2
tmp10p2 = tmp4p2 + tmp5p2
tmp11p2 = tmp5p2 + tmp6p2
tmp12p2 = tmp6p2 + tmp7p2
z5p2 = (tmp10p2 - tmp12p2) * 0.382683433
z2p2 = 0.541196100 * tmp10p2 + z5p2
z4p2 = 1.306562965 * tmp12p2 + z5p2
z3p2 = tmp11p2 * 0.707106781
z11p2 = tmp7p2 + z3p2
z13p2 = tmp7p2 - z3p2
data[off + 40] = z13p2 + z2p2
data[off + 24] = z13p2 - z2p2
data[off + 8] = z11p2 + z4p2
data[off + 56] = z11p2 - z4p2
off += 1
end
fDCTQ = nil
64.times do |i|
fDCTQ = data[i].to_f * fdtbl[i]
@output_fDCTQ[i] = (fDCTQ > 0.0) ? (fDCTQ + 0.5).floor : (fDCTQ - 0.5).ceil
end
@output_fDCTQ
end
def write_APP0
write_word 0xFFE0 # marker
write_word 16 # length
write_byte 0x4A # J
write_byte 0x46 # F
write_byte 0x49 # I
write_byte 0x46 # F
write_byte 0 # = "JFIF",'\0'
write_byte 1 # versionhi
write_byte 1 # versionlo
write_byte 0 # xyunits
write_word 1 # xdensity
write_word 1 # ydensity
write_byte 0 # thumbnwidth
write_byte 0 # thumbnheight
end
def write_DQT
write_word 0xFFDB # marker
write_word 132 # length
write_byte 0
64.times do |i|
write_byte @YTable[i]
end
write_byte 1
64.times do |i|
write_byte @UVTable[i]
end
end
def write_SOF0 width, height
write_word 0xFFC0 # marker
write_word 17 # length, truecolor YUV JPG
write_byte 8 # precision
write_word height
write_word width
write_byte 3 # nrofcomponents
write_byte 1 # IdY
write_byte 0x11 # HVY
write_byte 0 # QTY
write_byte 2 # IdU
write_byte 0x11 # HVU
write_byte 1 # QTU
write_byte 3 # IdV
write_byte 0x11 # HVV
write_byte 1 # QTV
end
def write_DHT
write_word 0xFFC4 # marker
write_word 0x01A2 # length
write_byte 0 # HTYDCinfo
16.times do |i|
write_byte @std_dc_luminance_nrcodes[i+1]
end
12.times do |i|
write_byte @std_dc_luminance_values[i]
end
write_byte 0x10 # HTYACinfo
16.times do |i|
write_byte @std_ac_luminance_nrcodes[i+1]
end
162.times do |i|
write_byte @std_ac_luminance_values[i]
end
write_byte 1 # HTUDCinfo
16.times do |i|
write_byte @std_dc_chrominance_nrcodes[i+1]
end
12.times do |i|
write_byte @std_dc_chrominance_values[i]
end
write_byte 0x11 # HTUACinfo
16.times do |i|
write_byte @std_ac_chrominance_nrcodes[i+1]
end
162.times do |i|
write_byte @std_ac_chrominance_values[i]
end
end
def write_SOS
write_word 0xFFDA # marker
write_word 12 # length
write_byte 3 # nrofcomponents
write_byte 1 # IdY
write_byte 0 # HTY
write_byte 2 # IdU
write_byte 0x11 # HTU
write_byte 3 # IdV
write_byte 0x11 # HTV
write_byte 0 # Ss
write_byte 0x3f # Se
write_byte 0 # Bf
end
def write_bits bs
value = bs[0]
posval = bs[1] - 1
while posval >= 0 do
if (value & (1 << posval)) != 0
@bytenew = @bytenew | (1 << @bytepos)
end
posval -= 1
@bytepos -= 1
if @bytepos < 0
if @bytenew == 0xFF
write_byte 0xFF
write_byte 0
else
write_byte @bytenew
end
@bytepos = 7
@bytenew = 0
end
end
end
def write_byte value
@byteout.push [value].pack('C*')
end
def write_word value
write_byte((value >> 8) & 0xFF)
write_byte( value & 0xFF)
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment