image_filter_dsl 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/bin/image_filter_dsl +10 -0
- data/lib/image_filter_dsl.rb +47 -0
- data/lib/image_filter_dsl/binary/serialize.rb +175 -0
- data/lib/image_filter_dsl/binary/struct.rb +98 -0
- data/lib/image_filter_dsl/dsl/filter.rb +47 -0
- data/lib/image_filter_dsl/dsl/filter_instructions.rb +225 -0
- data/lib/image_filter_dsl/dsl/kernel.rb +85 -0
- data/lib/image_filter_dsl/engine/image_processor.rb +199 -0
- data/lib/image_filter_dsl/engine/io.rb +54 -0
- metadata +54 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: dad7597cc47d2d15fbba4c15de8222bd7fd60599709c34801d909ba1b8c4e9e3
|
4
|
+
data.tar.gz: 53dc7843ef41747dc96baac57a440da580821c6b1ff18e3ecad479846343c51d
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 18274358415905e80d5969243832d30847496bc219510d3a11d5fd88be347c7a5de70eeb8415095413c684999beb24a269f5137b3a88c58bb223b6e78fb05a20
|
7
|
+
data.tar.gz: 61fcf052a2afb9f8445fcda605c6e94a84007680d55c5bda9bff4b995f5ff0854f3ba674b072a99da10f6ba037b5991b8308d563f270e73545d0ee95610908d1
|
@@ -0,0 +1,47 @@
|
|
1
|
+
require_relative './image_filter_dsl/dsl/filter.rb'
|
2
|
+
require_relative './image_filter_dsl/dsl/filter_instructions.rb'
|
3
|
+
require_relative './image_filter_dsl/dsl/kernel.rb'
|
4
|
+
require_relative './image_filter_dsl/binary/struct.rb'
|
5
|
+
require_relative './image_filter_dsl/binary/serialize.rb'
|
6
|
+
require_relative './image_filter_dsl/engine/io.rb'
|
7
|
+
require_relative './image_filter_dsl/engine/image_processor.rb'
|
8
|
+
|
9
|
+
##
|
10
|
+
# Image Filter DSL Library
|
11
|
+
# (c) 2018 VDTDEV/Wade H. ~ MIT License
|
12
|
+
# @author Wade H. <vdtdev@gmail.com>
|
13
|
+
module ImageFilterDsl
|
14
|
+
|
15
|
+
##
|
16
|
+
# Reference to Filter module
|
17
|
+
Filter = Dsl::Filter
|
18
|
+
|
19
|
+
##
|
20
|
+
# Shortcut to ImageProcessor constructor
|
21
|
+
# @param [Dsl::Kernel::FilterKernel|String] kernel FilterKernel object or path to binary kernel file
|
22
|
+
# @param [Integer] threads How many threads for processor to use
|
23
|
+
# @return [Engine::ImageProcessor] new instance of image processor
|
24
|
+
def self.image_processor(kernel, threads=6)
|
25
|
+
Engine::ImageProcessor.new(kernel, threads)
|
26
|
+
end
|
27
|
+
|
28
|
+
##
|
29
|
+
# Execute process image using kernel from CLI
|
30
|
+
# @param [Array] args Array of arguments (kernel file, img in, img out)
|
31
|
+
def self.cli_process_image(args)
|
32
|
+
kernel_file = args[0]
|
33
|
+
img_in = args[1]
|
34
|
+
img_out = args[2]
|
35
|
+
ip = image_processor(kernel_file)
|
36
|
+
ip.process_image(img_in, img_out)
|
37
|
+
end
|
38
|
+
|
39
|
+
##
|
40
|
+
# Shorthand for Engine::IO.write
|
41
|
+
# @param [Dsl::Kernel::FilterKernel] filter Filter to write
|
42
|
+
# @param [String] filename Filename to write to
|
43
|
+
def self.save_binary_kernel(filter, filename)
|
44
|
+
Engine::IO.write(filename, filter)
|
45
|
+
end
|
46
|
+
|
47
|
+
end
|
@@ -0,0 +1,175 @@
|
|
1
|
+
##
|
2
|
+
# Image Filter DSL Library
|
3
|
+
# (c) 2018 VDTDEV/Wade H. ~ MIT License
|
4
|
+
# @author Wade H. <vdtdev@gmail.com>
|
5
|
+
module ImageFilterDsl
|
6
|
+
module Binary
|
7
|
+
##
|
8
|
+
# Module providing serialization functionality for converting between
|
9
|
+
# FilterKernel instance and binary IfdKernel record
|
10
|
+
module Serialize
|
11
|
+
|
12
|
+
# @!group Object -> Binary
|
13
|
+
|
14
|
+
##
|
15
|
+
# Alias for Serialize::from_kernel
|
16
|
+
def self.to_record(filter)
|
17
|
+
from_kernel(filter)
|
18
|
+
end
|
19
|
+
|
20
|
+
##
|
21
|
+
# Build binary IfdKernel record from Kernel object
|
22
|
+
# @param [Dsl::Kernel::FilterKernel] filter Source filter kernel
|
23
|
+
# @return [Binary::Struct::IfdKernel] Binary kernel record
|
24
|
+
def self.from_kernel(filter)
|
25
|
+
variables = {
|
26
|
+
inputs: nil,
|
27
|
+
outputs: nil,
|
28
|
+
pool: nil,
|
29
|
+
symbols: nil
|
30
|
+
}
|
31
|
+
|
32
|
+
parts = {}
|
33
|
+
|
34
|
+
# Collect variables
|
35
|
+
pool = (filter.inputs + filter.outputs).uniq
|
36
|
+
filter.instructions.each do |i|
|
37
|
+
pool += i.inputs
|
38
|
+
pool += [i.out]
|
39
|
+
pool.uniq!
|
40
|
+
end
|
41
|
+
variables[:pool] = pool
|
42
|
+
|
43
|
+
# Generate variable symbols
|
44
|
+
variables[:symbols] = {}
|
45
|
+
count = 1
|
46
|
+
variables[:pool].each do |v|
|
47
|
+
variables[:symbols][v] = count
|
48
|
+
count += 1
|
49
|
+
end
|
50
|
+
|
51
|
+
# Build variables record
|
52
|
+
|
53
|
+
vars_rec = Binary::Struct::IfdVariables.new(
|
54
|
+
field_count: variables[:pool].length,
|
55
|
+
input_count: filter.inputs.length,
|
56
|
+
output_count: filter.outputs.length,
|
57
|
+
input_fields: filter.inputs.map { |i| variables[:symbols][i] },
|
58
|
+
output_fields: filter.outputs.map { |i| variables[:symbols][i] }
|
59
|
+
)
|
60
|
+
|
61
|
+
fkinds = Binary::Struct::FIELD_KINDS
|
62
|
+
|
63
|
+
defs = variables[:symbols].keys.map do |k|
|
64
|
+
f_field = Binary::Struct::IfdField.new(
|
65
|
+
symbol: variables[:symbols][k]
|
66
|
+
)
|
67
|
+
|
68
|
+
if k.is_a?(Symbol)
|
69
|
+
f_field.kind = fkinds[:var]
|
70
|
+
f_field.name_length = k.to_s.length
|
71
|
+
f_field.variable.assign(k.to_s)
|
72
|
+
elsif k.is_a?(Integer)
|
73
|
+
f_field.kind = fkinds[:literal_int]
|
74
|
+
f_field.int_value = k
|
75
|
+
else
|
76
|
+
f_field.kind = fkinds[:literal_float]
|
77
|
+
f_field.float_value = k
|
78
|
+
end
|
79
|
+
|
80
|
+
f_field
|
81
|
+
end
|
82
|
+
|
83
|
+
vars_rec.definition = defs
|
84
|
+
parts[:variables] = vars_rec
|
85
|
+
|
86
|
+
# Build instructions records
|
87
|
+
parts[:instructions] = filter.instructions.map do |i|
|
88
|
+
inp_fields = i.inputs.map { |f| variables[:symbols][f] }
|
89
|
+
f_ins = Binary::Struct::IfdInstruction.new(
|
90
|
+
operation: Binary::Struct.instruction_symbol(i.op),
|
91
|
+
input_count: i.inputs.length,
|
92
|
+
input_fields: inp_fields,
|
93
|
+
output_field: variables[:symbols][i.out]
|
94
|
+
)
|
95
|
+
f_ins
|
96
|
+
end
|
97
|
+
|
98
|
+
# Build Kernel Record
|
99
|
+
kernel = Binary::Struct::IfdKernel.new(
|
100
|
+
header: Binary::Struct::IfdHeader.new,
|
101
|
+
variables: parts[:variables],
|
102
|
+
instruction_count: filter.instructions.length,
|
103
|
+
instructions: parts[:instructions]
|
104
|
+
)
|
105
|
+
|
106
|
+
return kernel
|
107
|
+
end
|
108
|
+
|
109
|
+
# @!endgroup
|
110
|
+
|
111
|
+
# @!group Binary -> Object
|
112
|
+
|
113
|
+
##
|
114
|
+
# Alias for Serialize::from_record
|
115
|
+
def self.to_kernel(record)
|
116
|
+
from_record(record)
|
117
|
+
end
|
118
|
+
|
119
|
+
##
|
120
|
+
# Convert IfdKernel record to FilterKernel object
|
121
|
+
# @param [Binary::Struct::IfdKernel] record Record to convert
|
122
|
+
# @return [Dsl::Kernel::FilterKernel] FilterKernel from record
|
123
|
+
def self.from_record(record)
|
124
|
+
ins = []
|
125
|
+
outs = []
|
126
|
+
|
127
|
+
vars = record.variables
|
128
|
+
vardefs = vars.definition
|
129
|
+
|
130
|
+
vars_val = Proc.new do |vs,force_string=false|
|
131
|
+
r = nil
|
132
|
+
i = vardefs.select{|v| v.symbol == vs}[0]
|
133
|
+
if i.kind == Binary::Struct::FIELD_KINDS[:var]
|
134
|
+
r = i.variable.strip.to_sym
|
135
|
+
r = ":#{r.to_s}" if force_string
|
136
|
+
elsif i.kind == Binary::Struct::FIELD_KINDS[:literal_int]
|
137
|
+
r = i.int_value
|
138
|
+
else
|
139
|
+
r = i.float_value
|
140
|
+
end
|
141
|
+
r
|
142
|
+
end
|
143
|
+
|
144
|
+
op_sym = Proc.new do |opc|
|
145
|
+
Dsl::FilterInstructions::OP_INS.select{|k,v| v==opc}.keys[0]
|
146
|
+
end
|
147
|
+
|
148
|
+
ins = vars.input_fields.map { |f|
|
149
|
+
vardefs.select{|v| v.symbol == f}[0].variable.strip.to_sym
|
150
|
+
}
|
151
|
+
|
152
|
+
outs = vars.output_fields.map { |f|
|
153
|
+
vardefs.select{|v| v.symbol == f}[0].variable.strip.to_sym
|
154
|
+
}
|
155
|
+
|
156
|
+
instructions = record.instructions.map do |i|
|
157
|
+
[
|
158
|
+
"#{op_sym.call(i.operation)} ",
|
159
|
+
i.input_fields.map{|f| vars_val.call(f) }.to_s,
|
160
|
+
" , ", vars_val.call(i.output_field, true).to_s
|
161
|
+
].join("")
|
162
|
+
end
|
163
|
+
|
164
|
+
f = Dsl::Filter.define(ins, outs){
|
165
|
+
self.instance_eval(instructions.join("\n"))
|
166
|
+
}
|
167
|
+
|
168
|
+
return f
|
169
|
+
end
|
170
|
+
|
171
|
+
# @!endgroup
|
172
|
+
|
173
|
+
end
|
174
|
+
end
|
175
|
+
end
|
@@ -0,0 +1,98 @@
|
|
1
|
+
require 'bindata'
|
2
|
+
##
|
3
|
+
# Image Filter DSL Library
|
4
|
+
# (c) 2018 VDTDEV/Wade H. ~ MIT License
|
5
|
+
# @author Wade H. <vdtdev@gmail.com>
|
6
|
+
module ImageFilterDsl
|
7
|
+
module Binary
|
8
|
+
##
|
9
|
+
# Data structures used for storing Filter Kernels
|
10
|
+
# in binary format
|
11
|
+
module Struct
|
12
|
+
|
13
|
+
##
|
14
|
+
# Symbols used to indicate kind of a field
|
15
|
+
FIELD_KINDS = {
|
16
|
+
var: 0x1a,
|
17
|
+
literal_int: 0x12,
|
18
|
+
literal_float: 0x1f
|
19
|
+
}
|
20
|
+
|
21
|
+
##
|
22
|
+
# Constants used in header
|
23
|
+
HEADER_VALUES = {
|
24
|
+
header: "ifdKernel",
|
25
|
+
version: 0.01
|
26
|
+
}
|
27
|
+
|
28
|
+
##
|
29
|
+
# Look up hex symbol for instruction in FilterInstructions module
|
30
|
+
# @param [Symbol] ins Instruction to look up
|
31
|
+
# @return [Integer] Instruction hex symbol
|
32
|
+
def self.instruction_symbol(ins)
|
33
|
+
Dsl::FilterInstructions::OP_INS[ins]
|
34
|
+
end
|
35
|
+
|
36
|
+
##
|
37
|
+
# Image Filter DSL Field record
|
38
|
+
class IfdField < BinData::Record
|
39
|
+
uint16le :symbol
|
40
|
+
uint16le :kind
|
41
|
+
uint8le :name_length
|
42
|
+
string :variable, :length => :name_length,
|
43
|
+
:onlyif => lambda { kind == FIELD_KINDS[:var] }
|
44
|
+
int32le :int_value,
|
45
|
+
:onlyif => lambda { kind == FIELD_KINDS[:literal_int] }
|
46
|
+
float_le :float_value,
|
47
|
+
:onlyif => lambda { kind == FIELD_KINDS[:literal_float] }
|
48
|
+
end
|
49
|
+
|
50
|
+
##
|
51
|
+
# Image Filter DSL Instruction record
|
52
|
+
class IfdInstruction < BinData::Record
|
53
|
+
uint16le :operation
|
54
|
+
uint8le :input_count
|
55
|
+
array :input_fields, :initial_length => :input_count do
|
56
|
+
uint16le :symbol
|
57
|
+
end
|
58
|
+
uint16le :output_field
|
59
|
+
end
|
60
|
+
|
61
|
+
##
|
62
|
+
# Image Filter DSL Variables definition record
|
63
|
+
class IfdVariables < BinData::Record
|
64
|
+
uint8le :field_count
|
65
|
+
uint8le :input_count
|
66
|
+
uint8le :output_count
|
67
|
+
array :definition, :initial_length => :field_count do
|
68
|
+
ifd_field :field
|
69
|
+
end
|
70
|
+
array :input_fields, :initial_length => :input_count do
|
71
|
+
uint16le :field
|
72
|
+
end
|
73
|
+
array :output_fields, :initial_length => :output_count do
|
74
|
+
uint16le :field
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
##
|
79
|
+
# Image Filter DSL Header record
|
80
|
+
class IfdHeader < BinData::Record
|
81
|
+
string :header, read_length: HEADER_VALUES[:header].length,
|
82
|
+
value: HEADER_VALUES[:header]
|
83
|
+
float_le :version, value: HEADER_VALUES[:version]
|
84
|
+
end
|
85
|
+
|
86
|
+
##
|
87
|
+
# Main Image Filter DSL Kernel record
|
88
|
+
class IfdKernel < BinData::Record
|
89
|
+
ifd_header :header
|
90
|
+
ifd_variables :variables
|
91
|
+
uint16le :instruction_count
|
92
|
+
array :instructions, :initial_length => :instruction_count do
|
93
|
+
ifd_instruction :instruction
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
##
|
2
|
+
# Image Filter DSL Library
|
3
|
+
# (c) 2018 VDTDEV/Wade H. ~ MIT License
|
4
|
+
# @author Wade H. <vdtdev@gmail.com>
|
5
|
+
module ImageFilterDsl
|
6
|
+
module Dsl
|
7
|
+
##
|
8
|
+
# Module used for declaring Filter w/ DSL
|
9
|
+
module Filter
|
10
|
+
|
11
|
+
##
|
12
|
+
# Define method
|
13
|
+
# @param [Array] ins Input symbols
|
14
|
+
# @param [Array] outs Output symbols
|
15
|
+
# @param [Proc] &block Filter instructions body
|
16
|
+
# @return [FilterKernel] new Filter Kernel
|
17
|
+
def self.define(ins,outs,&block)
|
18
|
+
kernel = Kernel::FilterKernel.new(ins,outs)
|
19
|
+
ip = processor(kernel)
|
20
|
+
ip.instance_eval &block
|
21
|
+
return kernel
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
##
|
27
|
+
# Process filter, creating new Instruction Processing module
|
28
|
+
# @param [FilterKernel] kernel Target FilterKernel
|
29
|
+
# @return [Module] module wrapping instructions to target kernel
|
30
|
+
def self.processor(kernel)
|
31
|
+
instruction_proc = Module.new
|
32
|
+
|
33
|
+
FilterInstructions::OPS.each do |op|
|
34
|
+
p = Proc.new do |ins,outs|
|
35
|
+
kernel.store_instruction(
|
36
|
+
Kernel::KernelInstruction.new(op,ins,outs)
|
37
|
+
)
|
38
|
+
end
|
39
|
+
instruction_proc.class.send(:define_method, op, p)
|
40
|
+
end
|
41
|
+
|
42
|
+
instruction_proc
|
43
|
+
end
|
44
|
+
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,225 @@
|
|
1
|
+
##
|
2
|
+
# Image Filter DSL Library
|
3
|
+
# (c) 2018 VDTDEV/Wade H. ~ MIT License
|
4
|
+
# @author Wade H. <vdtdev@gmail.com>
|
5
|
+
module ImageFilterDsl
|
6
|
+
module Dsl
|
7
|
+
##
|
8
|
+
# Module defining filter kernel instruction logic
|
9
|
+
module FilterInstructions
|
10
|
+
|
11
|
+
##
|
12
|
+
# Hash of binary instruction symbols
|
13
|
+
OP_INS = {
|
14
|
+
# math = 0x0X-0xAF
|
15
|
+
add: 0x01,
|
16
|
+
mult: 0x02,
|
17
|
+
div: 0x03,
|
18
|
+
mod: 0x04,
|
19
|
+
abs: 0x05,
|
20
|
+
# collection op = 0xB0-0xCF
|
21
|
+
min: 0xb0,
|
22
|
+
max: 0xb1,
|
23
|
+
avg: 0xb2,
|
24
|
+
# logic/mem/conv op = 0xD0-??
|
25
|
+
copy: 0xd0,
|
26
|
+
above: 0xd1,
|
27
|
+
below: 0xd2,
|
28
|
+
floor: 0xd3,
|
29
|
+
ceil: 0xd4,
|
30
|
+
float: 0xd5,
|
31
|
+
round: 0xd6,
|
32
|
+
switch: 0xd7,
|
33
|
+
eq: 0xd8,
|
34
|
+
bnot: 0xd9
|
35
|
+
}
|
36
|
+
|
37
|
+
##
|
38
|
+
# Array of all valid filter instructions
|
39
|
+
OPS = OP_INS.keys
|
40
|
+
|
41
|
+
##
|
42
|
+
# Add instruction
|
43
|
+
# @param [Array] input values
|
44
|
+
# @return [Integer|Float] output value
|
45
|
+
def self.add(i)
|
46
|
+
i.sum
|
47
|
+
end
|
48
|
+
|
49
|
+
##
|
50
|
+
# Multiply instruction
|
51
|
+
# @param [Array] input values
|
52
|
+
# @return [Integer|Float] output value
|
53
|
+
def self.mult(i)
|
54
|
+
v=1;
|
55
|
+
i.each{|n|v=v*n}
|
56
|
+
v
|
57
|
+
end
|
58
|
+
|
59
|
+
##
|
60
|
+
# Multiply instruction
|
61
|
+
# @param [Array] input values
|
62
|
+
# @return [Integer|Float] output value
|
63
|
+
def self.div(i)
|
64
|
+
i[0]/i[1]
|
65
|
+
end
|
66
|
+
##
|
67
|
+
# Calculate modulo
|
68
|
+
# @param [Array] input values
|
69
|
+
# @return [Integer|] output value
|
70
|
+
def self.mod(i)
|
71
|
+
i[0] % i[1]
|
72
|
+
end
|
73
|
+
|
74
|
+
##
|
75
|
+
# Absolute value
|
76
|
+
# @param [Array] input value
|
77
|
+
# @return [Integer|Float] output value
|
78
|
+
def self.abs(i)
|
79
|
+
i[0].abs
|
80
|
+
end
|
81
|
+
|
82
|
+
##
|
83
|
+
# Minimum instruction
|
84
|
+
# @param [Array] input values
|
85
|
+
# @return [Integer|Float] output value
|
86
|
+
def self.min(i)
|
87
|
+
i.min
|
88
|
+
end
|
89
|
+
|
90
|
+
##
|
91
|
+
# Maximum instruction
|
92
|
+
# @param [Array] input values
|
93
|
+
# @return [Integer|Float] output value
|
94
|
+
def self.max(i)
|
95
|
+
i.max
|
96
|
+
end
|
97
|
+
|
98
|
+
##
|
99
|
+
# Average instruction
|
100
|
+
# @param [Array] input values
|
101
|
+
# @return [Integer|Float] output value
|
102
|
+
def self.avg(i)
|
103
|
+
i.sum / (1.0 * i.length)
|
104
|
+
end
|
105
|
+
|
106
|
+
##
|
107
|
+
# Copy instruction
|
108
|
+
# @param [Array] input values (src)
|
109
|
+
# @return [Integer|Float] output value
|
110
|
+
def self.copy(i)
|
111
|
+
i[0]
|
112
|
+
end
|
113
|
+
|
114
|
+
##
|
115
|
+
# Above instruction
|
116
|
+
# @param [Array] input values (a,b,trueVal,falseVal)
|
117
|
+
# @return [Integer|Float] output value
|
118
|
+
def self.above(i)
|
119
|
+
if(i[0]>i[1])
|
120
|
+
if i.length < 3
|
121
|
+
1
|
122
|
+
else
|
123
|
+
i[2]
|
124
|
+
end
|
125
|
+
else
|
126
|
+
if i.length < 4
|
127
|
+
0
|
128
|
+
else
|
129
|
+
i[3]
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
##
|
135
|
+
# Below instruction
|
136
|
+
# @param [Array] input values (a,b,trueVal,falseVal)
|
137
|
+
# @return [Integer|Float] output value
|
138
|
+
def self.below(i)
|
139
|
+
if(i[0]<i[1])
|
140
|
+
if i.length < 3
|
141
|
+
1
|
142
|
+
else
|
143
|
+
i[2]
|
144
|
+
end
|
145
|
+
else
|
146
|
+
if i.length < 4
|
147
|
+
0
|
148
|
+
else
|
149
|
+
i[3]
|
150
|
+
end
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
##
|
155
|
+
# Floor instruction
|
156
|
+
# @param [Array] input value (v)
|
157
|
+
# @return [Integer|Float] output value
|
158
|
+
def self.floor(i)
|
159
|
+
i[0].floor
|
160
|
+
end
|
161
|
+
|
162
|
+
##
|
163
|
+
# Ceil instruction
|
164
|
+
# @param [Array] input value (v)
|
165
|
+
# @return [Integer|Float] output value
|
166
|
+
def self.ceil(i)
|
167
|
+
i[0].ceil
|
168
|
+
end
|
169
|
+
|
170
|
+
##
|
171
|
+
# Float cast instruction
|
172
|
+
# @param [Array] input value (v)
|
173
|
+
# @return [Integer|Float] output value
|
174
|
+
def self.float(i)
|
175
|
+
i[0].to_f
|
176
|
+
end
|
177
|
+
|
178
|
+
##
|
179
|
+
# Round instruction
|
180
|
+
# @param [Array] input value (val, decimal_places)
|
181
|
+
# @return [Integer|Float] output value
|
182
|
+
def self.round(i)
|
183
|
+
i[0].round(i[1])
|
184
|
+
end
|
185
|
+
|
186
|
+
##
|
187
|
+
# 'Switch' instruction (basically if)
|
188
|
+
#
|
189
|
+
# switch [condition (0/1/0.0/1.0), trueval, falseval]
|
190
|
+
# @param [Array] input value (condition, true val, false val)
|
191
|
+
# @return [Integer|Float] output value
|
192
|
+
def self.switch(i)
|
193
|
+
if i[0].to_i == 1
|
194
|
+
i[1]
|
195
|
+
else
|
196
|
+
i[2]
|
197
|
+
end
|
198
|
+
end
|
199
|
+
|
200
|
+
##
|
201
|
+
# Equal condition instruction
|
202
|
+
# @param [Array] input value (a, b)
|
203
|
+
# @return [Integer|Float] output value 1 true 0 falsew
|
204
|
+
def self.eq(i)
|
205
|
+
if i[0] == i[1]
|
206
|
+
1
|
207
|
+
else
|
208
|
+
0
|
209
|
+
end
|
210
|
+
end
|
211
|
+
|
212
|
+
##
|
213
|
+
# Logic invert instruction
|
214
|
+
# @param [Array] input value (0 or 1)
|
215
|
+
# @return [Integer|Float] output value (1 if in 0, 0 if in 1)
|
216
|
+
def self.bnot(i)
|
217
|
+
if i[0].to_i == 1
|
218
|
+
0
|
219
|
+
else
|
220
|
+
1
|
221
|
+
end
|
222
|
+
end
|
223
|
+
end
|
224
|
+
end
|
225
|
+
end
|
@@ -0,0 +1,85 @@
|
|
1
|
+
##
|
2
|
+
# Image Filter DSL Library
|
3
|
+
# (c) 2018 VDTDEV/Wade H. ~ MIT License
|
4
|
+
# @author Wade H. <vdtdev@gmail.com>
|
5
|
+
module ImageFilterDsl
|
6
|
+
module Dsl
|
7
|
+
module Kernel
|
8
|
+
##
|
9
|
+
# IFD Filter Kernel class
|
10
|
+
# @author vdtdev <vdtdev@gmail.com>
|
11
|
+
class FilterKernel
|
12
|
+
attr_accessor :instructions
|
13
|
+
attr_accessor :inputs
|
14
|
+
attr_accessor :outputs
|
15
|
+
|
16
|
+
##
|
17
|
+
# Kernel constructor
|
18
|
+
# @param [Array] inputs Input symbols
|
19
|
+
# @param [Array] outputs Output symbols
|
20
|
+
def initialize(inputs=[],outputs=[])
|
21
|
+
@inputs = inputs
|
22
|
+
@outputs = outputs
|
23
|
+
@instructions = []
|
24
|
+
end
|
25
|
+
|
26
|
+
##
|
27
|
+
# Store instruction in kernel
|
28
|
+
# @param [Kernel::KernelInstruction] instruction Instruction to add
|
29
|
+
# @return [Integer] total number of instructions
|
30
|
+
def store_instruction(instruction)
|
31
|
+
@instructions.append(instruction)
|
32
|
+
@instructions.length
|
33
|
+
end
|
34
|
+
|
35
|
+
##
|
36
|
+
# Process filter kernel and produce output hash
|
37
|
+
# @param [Hash] initial_values Hash of values for inputs
|
38
|
+
# @return [Hash] hash of result values for output symbols
|
39
|
+
def process(initial_values)
|
40
|
+
outs = Hash[* @outputs.map{|o| [o,nil] }.flatten]
|
41
|
+
@instructions.each do |i|
|
42
|
+
v = i.calculate(initial_values)
|
43
|
+
if @outputs.include?(i.out)
|
44
|
+
outs[i.out] = v
|
45
|
+
else
|
46
|
+
initial_values[i.out] = v
|
47
|
+
end
|
48
|
+
end
|
49
|
+
outs
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
##
|
54
|
+
# Class representing a FilterKernel instruction
|
55
|
+
class KernelInstruction
|
56
|
+
attr_accessor :op
|
57
|
+
attr_accessor :inputs
|
58
|
+
attr_accessor :out
|
59
|
+
|
60
|
+
##
|
61
|
+
# Construct new Filter Kernel Instruction
|
62
|
+
# @param [Symbol] op Instruction operation
|
63
|
+
# @param [Array] ins Input symbols
|
64
|
+
# @param [Symbol] out Output symbol
|
65
|
+
def initialize(op,ins,out)
|
66
|
+
@op = op
|
67
|
+
@inputs = ins
|
68
|
+
unless @inputs.kind_of?(Array)
|
69
|
+
@inputs = [@inputs]
|
70
|
+
end
|
71
|
+
@out = out
|
72
|
+
end
|
73
|
+
|
74
|
+
##
|
75
|
+
# Calculate instruction output
|
76
|
+
# @param [Hash] variables Hash of variable values
|
77
|
+
# @return [Integer|Float] output value
|
78
|
+
def calculate(variables)
|
79
|
+
vals = @inputs.map{|v| (v.kind_of?(Symbol))? variables[v] : v }
|
80
|
+
FilterInstructions.method(@op).call(vals)
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
@@ -0,0 +1,199 @@
|
|
1
|
+
require 'chunky_png'
|
2
|
+
|
3
|
+
##
|
4
|
+
# Image Filter DSL Library
|
5
|
+
# (c) 2018 VDTDEV/Wade H. ~ MIT License
|
6
|
+
# @author Wade H. <vdtdev@gmail.com>
|
7
|
+
module ImageFilterDsl
|
8
|
+
module Engine
|
9
|
+
##
|
10
|
+
# Image Processor class applies Filter Kernels to images
|
11
|
+
class ImageProcessor
|
12
|
+
|
13
|
+
attr_accessor :kernel, :config
|
14
|
+
|
15
|
+
##
|
16
|
+
# Constructor
|
17
|
+
# @param [Dsl::Kernel::FilterKernel|String] filter_kernel FilterKernel object or path to binary kernel file
|
18
|
+
# @param [Integer] thread_count How many threads for processor to use
|
19
|
+
def initialize(filter_kernel, thread_count=6)
|
20
|
+
@config = {
|
21
|
+
filter_kernel_filename: nil,
|
22
|
+
threads: thread_count
|
23
|
+
}
|
24
|
+
|
25
|
+
if filter_kernel.is_a?(String)
|
26
|
+
@config[:filter_kernel_filename] = filter_kernel
|
27
|
+
|
28
|
+
@kernel = Engine::IO.read(
|
29
|
+
filter_kernel,
|
30
|
+
Engine::IO::DataFormat::KERNEL
|
31
|
+
)
|
32
|
+
else
|
33
|
+
@kernel = filter_kernel
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
##
|
38
|
+
# Process image
|
39
|
+
# @param [String] img_src_filename Source image filename
|
40
|
+
# @param [String] img_out_filename Output image filename
|
41
|
+
def process_image(img_src_filename, img_out_filename)
|
42
|
+
img = load_image(img_src_filename)
|
43
|
+
|
44
|
+
size = [img[:image].width, img[:image].height]
|
45
|
+
p_count = size[0] * size[1]
|
46
|
+
|
47
|
+
p_part = p_count / @config[:threads]
|
48
|
+
thread_loads = [].fill(p_part, (0..(@config[:threads])-1))
|
49
|
+
|
50
|
+
if (p_part * @config[:threads]) < p_count
|
51
|
+
thread_loads[-1] += (p_count - (p_part * @config[:threads]))
|
52
|
+
end
|
53
|
+
|
54
|
+
ranges = thread_loads.map.with_index do |l,i|
|
55
|
+
start = thread_loads[0..i][0..-2].sum
|
56
|
+
fin = start + (l - 1)
|
57
|
+
(start..fin)
|
58
|
+
end
|
59
|
+
|
60
|
+
ranges = ranges.map{|r| r.map{|i| i }}
|
61
|
+
|
62
|
+
threads = []
|
63
|
+
|
64
|
+
(0..@config[:threads]-1).each do |i|
|
65
|
+
threads << Thread.new {
|
66
|
+
run_process(img, ranges[i])
|
67
|
+
}
|
68
|
+
end
|
69
|
+
|
70
|
+
threads.each { |th| th.join }
|
71
|
+
|
72
|
+
save_image(img, img_out_filename)
|
73
|
+
end
|
74
|
+
|
75
|
+
private
|
76
|
+
|
77
|
+
##
|
78
|
+
# Convert point index to x,y coord
|
79
|
+
# @param [Integer] p Point index
|
80
|
+
# @param [Integer] width Grid width
|
81
|
+
# @return [Hash] Coordinate {:x, :y}
|
82
|
+
def point_coord(p,width)
|
83
|
+
{
|
84
|
+
x: (p % width),
|
85
|
+
y: (p / width)
|
86
|
+
}
|
87
|
+
end
|
88
|
+
|
89
|
+
##
|
90
|
+
# Execute processing for point range
|
91
|
+
# Called inside Thread.new
|
92
|
+
# @param [Hash] data Image data
|
93
|
+
# @param [Array] range Array of range points
|
94
|
+
def run_process(data, range)
|
95
|
+
|
96
|
+
range.each do |p|
|
97
|
+
c = point_coord(p, data[:image].width)
|
98
|
+
process_pixel(data, c[:x], c[:y])
|
99
|
+
end
|
100
|
+
|
101
|
+
end
|
102
|
+
|
103
|
+
##
|
104
|
+
# Process pixel using filter
|
105
|
+
# @param [Hash] data Image data hash
|
106
|
+
# @param [Integer] x Source (possibly dest) X coordinate
|
107
|
+
# @param [Integer] y Source (possibly dest) Y coordinate
|
108
|
+
# @return [Hash] Copy of filter outputs
|
109
|
+
def process_pixel(data, x, y)
|
110
|
+
clr = ChunkyPNG::Color
|
111
|
+
pixel = data[:image].get_pixel(x,y)
|
112
|
+
|
113
|
+
# r = (clr.r(pixel)/255.0).round(2)
|
114
|
+
# g = (clr.g(pixel)/255.0).round(2)
|
115
|
+
# b = (clr.b(pixel)/255.0).round(2)
|
116
|
+
# a = (clr.a(pixel)/255.0).round(2)
|
117
|
+
|
118
|
+
r = clr.r(pixel)
|
119
|
+
g = clr.g(pixel)
|
120
|
+
b = clr.b(pixel)
|
121
|
+
a = clr.a(pixel)
|
122
|
+
|
123
|
+
|
124
|
+
inputs = Hash[*@kernel.inputs.map{|k|[k,0]}.flatten]
|
125
|
+
inputs[:x] = x if inputs.keys.include?(:x)
|
126
|
+
inputs[:y] = y if inputs.keys.include?(:y)
|
127
|
+
inputs[:r] = r if inputs.keys.include?(:r)
|
128
|
+
inputs[:g] = g if inputs.keys.include?(:g)
|
129
|
+
inputs[:b] = b if inputs.keys.include?(:b)
|
130
|
+
inputs[:a] = a if inputs.keys.include?(:a)
|
131
|
+
inputs[:width] = data[:image].width if inputs.keys.include?(:width)
|
132
|
+
inputs[:hght] = data[:image].height if inputs.keys.include?(:hght)
|
133
|
+
|
134
|
+
|
135
|
+
o = @kernel.process(inputs)
|
136
|
+
|
137
|
+
outputs = {
|
138
|
+
x: x, y: y, r: r, g: g, b: b, a: a
|
139
|
+
}
|
140
|
+
|
141
|
+
o.keys.each do |k|
|
142
|
+
if o[k] == Float::INFINITY
|
143
|
+
p "Infinity: #{k}"
|
144
|
+
end
|
145
|
+
outputs[k] = o[k].to_i
|
146
|
+
end
|
147
|
+
|
148
|
+
# o_color = clr.rgba(
|
149
|
+
# (255*outputs[:r]).to_i,
|
150
|
+
# (255*outputs[:g]).to_i,
|
151
|
+
# (255*outputs[:b]).to_i,
|
152
|
+
# (255*outputs[:a]).to_i
|
153
|
+
# )
|
154
|
+
|
155
|
+
o_color = clr.rgba(
|
156
|
+
outputs[:r].to_i,
|
157
|
+
outputs[:g].to_i,
|
158
|
+
outputs[:b].to_i,
|
159
|
+
outputs[:a].to_i
|
160
|
+
)
|
161
|
+
|
162
|
+
data[:image].set_pixel(
|
163
|
+
outputs[:x], outputs[:y],
|
164
|
+
o_color
|
165
|
+
)
|
166
|
+
end
|
167
|
+
|
168
|
+
##
|
169
|
+
# Load PNG image using ChunkyPNG
|
170
|
+
# @param [String] filename Filename of image to load
|
171
|
+
# @return [Hash] Hash with :stream as ChunkyPNG::Datastream and
|
172
|
+
# :image as ChunkyPNG::Image and :filename with original filename
|
173
|
+
def load_image(filename)
|
174
|
+
stream = ChunkyPNG::Datastream.from_file(filename)
|
175
|
+
image = ChunkyPNG::Image.from_datastream(stream)
|
176
|
+
|
177
|
+
return {
|
178
|
+
filename: filename,
|
179
|
+
stream: stream,
|
180
|
+
image: image
|
181
|
+
}
|
182
|
+
end
|
183
|
+
|
184
|
+
##
|
185
|
+
# Save image data to disk as PNG
|
186
|
+
# @param [Hash] data Image data as provided by load_image
|
187
|
+
# @param [String] filename Optional filename to save to; if none given,
|
188
|
+
# uses the original filename from data hash
|
189
|
+
# @return [File] file object written to
|
190
|
+
def save_image(data, filename = nil)
|
191
|
+
# If no filename given, use original
|
192
|
+
filename = data[:filename] if filename.nil?
|
193
|
+
stream = data[:image].to_datastream()
|
194
|
+
stream.save(filename)
|
195
|
+
end
|
196
|
+
|
197
|
+
end
|
198
|
+
end
|
199
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
##
|
2
|
+
# Image Filter DSL Library
|
3
|
+
# (c) 2018 VDTDEV/Wade H. ~ MIT License
|
4
|
+
# @author Wade H. <vdtdev@gmail.com>
|
5
|
+
module ImageFilterDsl
|
6
|
+
module Engine
|
7
|
+
##
|
8
|
+
# I/O methods for reading/writing Filter Kernel data to files
|
9
|
+
module IO
|
10
|
+
|
11
|
+
##
|
12
|
+
# Constants for format option used in read
|
13
|
+
module DataFormat
|
14
|
+
KERNEL = :kernel
|
15
|
+
RECORD = :record
|
16
|
+
end
|
17
|
+
|
18
|
+
##
|
19
|
+
# Read Filter Kernel from file
|
20
|
+
# @param [String] filename Filename to read from
|
21
|
+
# @param [Symbol] format Format to return (constants from
|
22
|
+
# DataFormat module; KERNEL or RECORD)
|
23
|
+
# @return [Dsl::Kernel::FilterKernel|Binary::Struct::IfdKernel] loaded data,
|
24
|
+
# in specified format
|
25
|
+
def self.read(filename, format=DataFormat::KERNEL)
|
26
|
+
record = Binary::Struct::IfdKernel.new
|
27
|
+
File.open(filename, 'rb'){|f| record.read(f) }
|
28
|
+
|
29
|
+
if format == DataFormat::RECORD
|
30
|
+
return record
|
31
|
+
else
|
32
|
+
return Binary::Serialize.to_kernel(record)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
##
|
37
|
+
# Write filter kernel to file
|
38
|
+
# @param [String] filename File name to write to
|
39
|
+
# @param [Binary::Struct::IfdKernel|Dsl::Kernel::FilterKernel] filter Filter kernel or record to write
|
40
|
+
# @return [Binary::Struct::IfdKernel] IfdKernel record
|
41
|
+
def self.write(filename, filter)
|
42
|
+
rec = nil
|
43
|
+
if filter.is_a?(Dsl::Kernel::FilterKernel)
|
44
|
+
rec = Binary::Serialize.to_record(filter)
|
45
|
+
elsif filter.is_a?(Binary::Struct::IfdKernel)
|
46
|
+
rec = filter
|
47
|
+
end
|
48
|
+
|
49
|
+
File.open(filename, 'wb') { |f| rec.write(f) }
|
50
|
+
end
|
51
|
+
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
metadata
ADDED
@@ -0,0 +1,54 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: image_filter_dsl
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Wade H.
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2018-07-02 00:00:00.000000000 Z
|
12
|
+
dependencies: []
|
13
|
+
description: Image Filter DSL with processing
|
14
|
+
email: vdtdev.prod@gmail.com
|
15
|
+
executables:
|
16
|
+
- image_filter_dsl
|
17
|
+
extensions: []
|
18
|
+
extra_rdoc_files: []
|
19
|
+
files:
|
20
|
+
- bin/image_filter_dsl
|
21
|
+
- lib/image_filter_dsl.rb
|
22
|
+
- lib/image_filter_dsl/binary/serialize.rb
|
23
|
+
- lib/image_filter_dsl/binary/struct.rb
|
24
|
+
- lib/image_filter_dsl/dsl/filter.rb
|
25
|
+
- lib/image_filter_dsl/dsl/filter_instructions.rb
|
26
|
+
- lib/image_filter_dsl/dsl/kernel.rb
|
27
|
+
- lib/image_filter_dsl/engine/image_processor.rb
|
28
|
+
- lib/image_filter_dsl/engine/io.rb
|
29
|
+
homepage: https://bitbucket.org/WadeH/image_filter_dsl/src
|
30
|
+
licenses:
|
31
|
+
- MIT
|
32
|
+
metadata:
|
33
|
+
source_code_uri: https://bitbucket.org/WadeH/image_filter_dsl/src
|
34
|
+
post_install_message:
|
35
|
+
rdoc_options: []
|
36
|
+
require_paths:
|
37
|
+
- lib
|
38
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
39
|
+
requirements:
|
40
|
+
- - ">="
|
41
|
+
- !ruby/object:Gem::Version
|
42
|
+
version: '0'
|
43
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
requirements: []
|
49
|
+
rubyforge_project:
|
50
|
+
rubygems_version: 2.7.8
|
51
|
+
signing_key:
|
52
|
+
specification_version: 4
|
53
|
+
summary: Image Filter DSL
|
54
|
+
test_files: []
|