Class: DXOpal::Image

Inherits:
RemoteResource show all
Defined in:
opal/dxopal/image.rb

Overview

Represents an image Each instance of Image has its own off-screen canvas.

Constant Summary collapse

BLEND_TYPES =
{
  alpha: "source-over", # A over B (Default)
  add:   "lighter"      # A + B
}

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Methods inherited from RemoteResource

[], _klass_name, _load_resources, add_class, register

Constructor Details

#initialize(width, height, color = C_DEFAULT, canvas: nil) ⇒ Image

Create an instance of Image



32
33
34
35
36
37
38
# File 'opal/dxopal/image.rb', line 32

def initialize(width, height, color=C_DEFAULT, canvas: nil)
  @width, @height = width, height
  @canvas = canvas || `document.createElement("canvas")`
  @ctx = `#{@canvas}.getContext('2d')`
  _resize(@width, @height)
  box_fill(0, 0, @width, @height, color)
end

Instance Attribute Details

#canvasObject (readonly)

Returns the value of attribute canvas



39
40
41
# File 'opal/dxopal/image.rb', line 39

def canvas
  @canvas
end

#ctxObject (readonly)

Returns the value of attribute ctx



39
40
41
# File 'opal/dxopal/image.rb', line 39

def ctx
  @ctx
end

#heightObject (readonly)

Returns the value of attribute height



39
40
41
# File 'opal/dxopal/image.rb', line 39

def height
  @height
end

#widthObject (readonly)

Returns the value of attribute width



39
40
41
# File 'opal/dxopal/image.rb', line 39

def width
  @width
end

Class Method Details

._load(path_or_url) ⇒ Object

Load remote image (called via Window.load_resources)



10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
# File 'opal/dxopal/image.rb', line 10

def self._load(path_or_url)
  raw_img = `new Image()`
  img_promise = %x{
    new Promise(function(resolve, reject) {
      raw_img.onload = function() {
        resolve(raw_img);
      };
      raw_img.src = path_or_url;
    });
  }

  img = new(0, 0)
  %x{
    #{img_promise}.then(function(raw_img){
      img.$_resize(raw_img.width, raw_img.height);
      img.$_draw_raw_image(0, 0, raw_img);
    });
  }
  return img, img_promise
end

Instance Method Details

#[](x, y) ⇒ Object

Get a pixel as ARGB array



116
117
118
119
120
121
122
123
124
125
# File 'opal/dxopal/image.rb', line 116

def [](x, y)
  ctx = @ctx
  ret = nil
  %x{
    var pixel = ctx.getImageData(x, y, 1, 1);
    var rgba = pixel.data;
    ret = [rgba[3], rgba[0], rgba[1], rgba[2]];
  }
  return ret
end

#[]=(x, y, color) ⇒ Object

Put a pixel on this image



128
129
130
# File 'opal/dxopal/image.rb', line 128

def []=(x, y, color)
  box_fill(x, y, x, y, color)
end

#_draw_raw_image(x, y, raw_img) ⇒ Object

Copy an <img> onto this image



286
287
288
289
290
# File 'opal/dxopal/image.rb', line 286

def _draw_raw_image(x, y, raw_img)
  %x{
    #{@ctx}.drawImage(#{raw_img}, x, y);
  }
end

#_image_data(x = 0, y = 0, w = @width, h = @height) ⇒ Object

Return .getImageData



293
294
295
# File 'opal/dxopal/image.rb', line 293

def _image_data(x=0, y=0, w=@width, h=@height)
  return `#{@ctx}.getImageData(x, y, w, h)`
end

#_put_image_data(image_data, x = 0, y = 0) ⇒ Object

Call .putImageData



298
299
300
# File 'opal/dxopal/image.rb', line 298

def _put_image_data(image_data, x=0, y=0)
  `#{@ctx}.putImageData(image_data, x, y)`
end

#_resize(w, h) ⇒ Object

Set size of this image



42
43
44
45
46
47
48
# File 'opal/dxopal/image.rb', line 42

def _resize(w, h)
  @width, @height = w, h
  %x{
    #{@canvas}.width = w;
    #{@canvas}.height = h;
  }
end

#_rgb(color) ⇒ Object

Return a string like 'rgb(255, 255, 255)' `color` is 3 or 4 numbers



304
305
306
307
308
309
310
311
312
313
314
315
# File 'opal/dxopal/image.rb', line 304

def _rgb(color)
  case color.length
  when 4
    # Just ignore alpha
    rgb = color[1, 3]
  when 3
    rgb = color
  else
    raise "invalid color: #{color.inspect}"
  end
  return "rgb(" + rgb.join(', ') + ")";
end

#_rgba(color) ⇒ Object

Return a string like 'rgba(255, 255, 255, 128)' `color` is 3 or 4 numbers



319
320
321
# File 'opal/dxopal/image.rb', line 319

def _rgba(color)
  return "rgba(" + _rgba_ary(color).join(', ') + ")"
end

#_rgba_ary(color) ⇒ Object

Return an array like `[255, 255, 255, 128]`



324
325
326
327
328
329
330
331
332
333
334
335
# File 'opal/dxopal/image.rb', line 324

def _rgba_ary(color)
  case color.length
  when 4
    # color is ARGB in DXRuby, so move A to the last
    color[1, 3] + [color[0]/255.0]
  when 3
    # Complement 255 as alpha 
    color + [1.0]
  else
    raise "invalid color: #{color.inspect}"
  end
end

#box(x1, y1, x2, y2, color) ⇒ Object

Draw a rectangle on this image



164
165
166
167
168
169
170
171
172
173
# File 'opal/dxopal/image.rb', line 164

def box(x1, y1, x2, y2, color)
  ctx = @ctx
  %x{
    ctx.beginPath();
    ctx.strokeStyle = #{_rgba(color)};
    ctx.rect(x1, y1, x2-x1+1, y2-y1+1); 
    ctx.stroke(); 
  }
  return self
end

#box_fill(x1, y1, x2, y2, color) ⇒ Object

Draw a filled box on this image



176
177
178
179
180
181
182
183
184
# File 'opal/dxopal/image.rb', line 176

def box_fill(x1, y1, x2, y2, color)
  ctx = @ctx
  %x{
    ctx.beginPath();
    ctx.fillStyle = #{_rgba(color)};
    ctx.fillRect(x1, y1, x2-x1+1, y2-y1+1); 
  }
  return self
end

#circle(x, y, r, color) ⇒ Object

Draw a circle on this image



187
188
189
190
191
192
193
194
195
196
# File 'opal/dxopal/image.rb', line 187

def circle(x, y, r, color)
  ctx = @ctx
  %x{
    ctx.beginPath();
    ctx.strokeStyle = #{_rgba(color)};
    ctx.arc(x, y, r, 0, Math.PI*2, false)
    ctx.stroke();
  }
  return self
end

#circle_fill(x, y, r, color) ⇒ Object

Draw a filled circle on this image



199
200
201
202
203
204
205
206
207
208
# File 'opal/dxopal/image.rb', line 199

def circle_fill(x, y, r, color)
  ctx = @ctx
  %x{
    ctx.beginPath();
    ctx.fillStyle = #{_rgba(color)};
    ctx.arc(x, y, r, 0, Math.PI*2, false)
    ctx.fill();
  }
  return self
end

#clearObject

Clear this image (i.e. fill with `[0,0,0,0]`)



245
246
247
# File 'opal/dxopal/image.rb', line 245

def clear
  fill([0, 0, 0, 0])
end

#compare(x, y, color) ⇒ Object

Return true if the pixel at `(x, y)` has the `color`



133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
# File 'opal/dxopal/image.rb', line 133

def compare(x, y, color)
  ctx = @ctx
  rgba1 = _rgba_ary(color)
  rgba2 = nil
  ret = nil
  %x{
    var pixel = ctx.getImageData(x, y, 1, 1);
    rgba2 = pixel.data;
    // TODO: what is the right way to compare an Array and an Uint8ClampedArray?
    ret = rgba1[0] == rgba2[0] &&
          rgba1[1] == rgba2[1] &&
          rgba1[2] == rgba2[2] &&
          rgba1[3] == rgba2[3]
  }
  return ret
end

#draw(x, y, image) ⇒ Object

Draw an Image on this image



51
52
53
54
55
56
# File 'opal/dxopal/image.rb', line 51

def draw(x, y, image)
  %x{
    #{@ctx}.drawImage(#{image.canvas}, x, y);
  }
  return self
end

#draw_ex(x, y, image, options = {}) ⇒ Object



78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
# File 'opal/dxopal/image.rb', line 78

def draw_ex(x, y, image, options={})
  scale_x = options[:scale_x] || 1
  scale_y = options[:scale_y] || 1
  center_x = options[:center_x] || image.width/2
  center_y = options[:center_y] || image.height/2 
  alpha = options[:alpha] || 255
  blend = options[:blend] || :alpha
  angle = options[:angle] || 0

  cx = x + center_x
  cy = y + center_y
  %x{
    #{@ctx}.translate(cx, cy);
    #{@ctx}.rotate(angle * Math.PI / 180.0);
    #{@ctx}.scale(scale_x, scale_y);
    #{@ctx}.save();
    #{@ctx}.globalAlpha = alpha / 255;
    #{@ctx}.globalCompositeOperation = #{BLEND_TYPES[blend]};
    #{@ctx}.drawImage(#{image.canvas}, x-cx, y-cy);
    #{@ctx}.restore();
    #{@ctx}.setTransform(1, 0, 0, 1, 0, 0); // reset
  }
  return self
end

#draw_font(x, y, string, font, color = [255,255,255]) ⇒ Object

Draw some text on this image



104
105
106
107
108
109
110
111
112
113
# File 'opal/dxopal/image.rb', line 104

def draw_font(x, y, string, font, color=[255,255,255])
  ctx = @ctx
  %x{
    ctx.font = #{font._spec_str};
    ctx.textBaseline = 'top';
    ctx.fillStyle = #{_rgba(color)};
    ctx.fillText(string, x, y);
  }
  return self
end

#draw_rot(x, y, image, angle, center_x = nil, center_y = nil) ⇒ Object

Draw an Image on this image with rotation

  • angle: Rotation angle (radian)

  • center_x, center_y: Rotation center in the `image` (default: center of the `image`)



69
70
71
# File 'opal/dxopal/image.rb', line 69

def draw_rot(x, y, image, angle, center_x=nil, center_y=nil)
  draw_ex(x, y, image, angle: angle, center_x: center_x, center_y: center_y)
end

#draw_scale(x, y, image, scale_x, scale_y, center_x = nil, center_y = nil) ⇒ Object

Draw an Image on this image with scaling

  • scale_x, scale_y: scaling factor (eg. 1.5)

  • center_x, center_y: scaling center (in other words, the point which does not move by this scaling. Default: image center)



62
63
64
# File 'opal/dxopal/image.rb', line 62

def draw_scale(x, y, image, scale_x, scale_y, center_x=nil, center_y=nil)
  draw_ex(x, y, image, scale_x: scale_x, scale_y: scale_y, center_x: center_x, center_y: center_y)
end

#fill(color) ⇒ Object

Fill this image with `color`



240
241
242
# File 'opal/dxopal/image.rb', line 240

def fill(color)
  box_fill(0, 0, @width-1, @height-1, color)
end

#line(x1, y1, x2, y2, color) ⇒ Object

Draw a line on this image



151
152
153
154
155
156
157
158
159
160
161
# File 'opal/dxopal/image.rb', line 151

def line(x1, y1, x2, y2, color)
  ctx = @ctx
  %x{
    ctx.beginPath();
    ctx.strokeStyle = #{_rgba(color)};
    ctx.moveTo(x1, y1); 
    ctx.lineTo(x2, y2); 
    ctx.stroke(); 
  }
  return self
end

#set_color_key(color) ⇒ Object

Set alpha of the pixels of the given color to 0

  • color : RGB color (If ARGV color is given, A is just ignored)



270
271
272
273
274
275
276
277
278
279
280
281
282
283
# File 'opal/dxopal/image.rb', line 270

def set_color_key(color)
  r, g, b, _ = _rgba_ary(color)
  data = _image_data()
  %x{
    var buf = data.data;

    for(var i = 0; i < buf.length; i += 4){
      if (buf[i] == r && buf[i+1] == g && buf[i+2] == b) {
        buf[i+3] = 0
      }
    }
  }
  _put_image_data(data)
end

#slice(x, y, width, height) ⇒ Object

Return an Image which is a copy of the specified area



250
251
252
253
254
255
# File 'opal/dxopal/image.rb', line 250

def slice(x, y, width, height)
  newimg = Image.new(width, height)
  data = _image_data(x, y, width, height)
  newimg._put_image_data(data)
  return newimg
end

#slice_tiles(xcount, ycount) ⇒ Object

Slice this image into xcount*ycount tiles



258
259
260
261
262
263
264
265
266
# File 'opal/dxopal/image.rb', line 258

def slice_tiles(xcount, ycount)
  tile_w = @width / xcount
  tile_h = @height / ycount
  return (0...ycount).flat_map{|v|
    (0...xcount).map{|u|
      slice(tile_w * u, tile_h * v, tile_w, tile_h)
    }
  }
end

#triangle(x1, y1, x2, y2, x3, y3, color) ⇒ Object

Draw a triangle on this image



211
212
213
214
215
216
217
218
219
220
221
222
223
# File 'opal/dxopal/image.rb', line 211

def triangle(x1, y1, x2, y2, x3, y3, color)
  ctx = @ctx
  %x{
    ctx.beginPath();
    ctx.strokeStyle = #{_rgba(color)};
    ctx.moveTo(x1, y1);
    ctx.lineTo(x2, y2);
    ctx.lineTo(x3, y3);
    ctx.lineTo(x1, y1);
    ctx.stroke();
  }
  return self
end

#triangle_fill(x1, y1, x2, y2, x3, y3, color) ⇒ Object

Draw a filled triangle on this image



226
227
228
229
230
231
232
233
234
235
236
237
# File 'opal/dxopal/image.rb', line 226

def triangle_fill(x1, y1, x2, y2, x3, y3, color)
  ctx = @ctx
  %x{
    ctx.beginPath();
    ctx.fillStyle = #{_rgba(color)};
    ctx.moveTo(x1, y1);
    ctx.lineTo(x2, y2);
    ctx.lineTo(x3, y3);
    ctx.fill();
  }
  return self
end