Class: DXOpal::Image

Inherits:
RemoteResource show all
Defined in:
lib/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



91
92
93
94
95
96
97
# File 'lib/dxopal/image.rb', line 91

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.



98
99
100
# File 'lib/dxopal/image.rb', line 98

def canvas
  @canvas
end

#ctxObject (readonly)

Returns the value of attribute ctx.



98
99
100
# File 'lib/dxopal/image.rb', line 98

def ctx
  @ctx
end

#heightObject (readonly)

Returns the value of attribute height.



98
99
100
# File 'lib/dxopal/image.rb', line 98

def height
  @height
end

#loadedObject

Returns the value of attribute loaded.



59
60
61
# File 'lib/dxopal/image.rb', line 59

def loaded
  @loaded
end

#promiseObject

Returns the value of attribute promise.



59
60
61
# File 'lib/dxopal/image.rb', line 59

def promise
  @promise
end

#widthObject (readonly)

Returns the value of attribute width.



98
99
100
# File 'lib/dxopal/image.rb', line 98

def width
  @width
end

Class Method Details

._load(path_or_url) ⇒ Object

Load remote image (called via Window.load_resources)



38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
# File 'lib/dxopal/image.rb', line 38

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

.hsl2rgb(h, s, l) ⇒ Object

Convert HSL to RGB (DXOpal original; not in DXRuby) h: 0-359 s: 0-100 l: 0-100



13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
# File 'lib/dxopal/image.rb', line 13

def self.hsl2rgb(h, s, l)
  if l < 50
    max = 2.55 * (l + l*(s/100.0))
    min = 2.55 * (l - l*(s/100.0))
  else
    max = 2.55 * (l + (100-l)*(s/100.0))
    min = 2.55 * (l - (100-l)*(s/100.0))
  end
  case h
  when 0...60
    [max, (h/60.0)*(max-min) + min, min]
  when 60...120
    [((120-h)/60.0)*(max-min) + min, max, min]
  when 120...180
    [min, max, ((h-120)/60.0)*(max-min) + min]
  when 180...240
    [min, ((240-h)/60.0)*(max-min) + min, max]
  when 240...300
    [((h-240)/60.0)*(max-min) + min, min, max]
  else
    [max, min, ((360-h)/60.0)*(max-min) + min]
  end
end

.load(path_or_url) ⇒ Object



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

def self.load(path_or_url)
  return new(1, 1).load(path_or_url)
end

Instance Method Details

#[](x, y) ⇒ Object

Get a pixel as ARGB array



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

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



187
188
189
# File 'lib/dxopal/image.rb', line 187

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

#_draw_raw_image(x, y, raw_img) ⇒ Object

Copy an <img> onto this image



345
346
347
348
349
# File 'lib/dxopal/image.rb', line 345

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



352
353
354
# File 'lib/dxopal/image.rb', line 352

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



357
358
359
# File 'lib/dxopal/image.rb', line 357

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



101
102
103
104
105
106
107
# File 'lib/dxopal/image.rb', line 101

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



363
364
365
366
367
368
369
370
371
372
373
374
# File 'lib/dxopal/image.rb', line 363

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



378
379
380
# File 'lib/dxopal/image.rb', line 378

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

#_rgba_ary(color) ⇒ Object

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



383
384
385
386
387
388
389
390
391
392
393
394
# File 'lib/dxopal/image.rb', line 383

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



223
224
225
226
227
228
229
230
231
232
# File 'lib/dxopal/image.rb', line 223

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

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

Draw a filled box on this image



235
236
237
238
239
240
241
242
243
# File 'lib/dxopal/image.rb', line 235

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

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

Draw a circle on this image



246
247
248
249
250
251
252
253
254
255
# File 'lib/dxopal/image.rb', line 246

def circle(x, y, r, color)
  ctx = @ctx
  %x{
    ctx.beginPath();
    ctx.strokeStyle = #{_rgba(color)};
    ctx.arc(x+0.5, y+0.5, 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



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

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]`)



304
305
306
# File 'lib/dxopal/image.rb', line 304

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

#compare(x, y, color) ⇒ Object

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



192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
# File 'lib/dxopal/image.rb', line 192

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



110
111
112
113
114
115
# File 'lib/dxopal/image.rb', line 110

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

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



137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
# File 'lib/dxopal/image.rb', line 137

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



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

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`)



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

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)



121
122
123
# File 'lib/dxopal/image.rb', line 121

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`



299
300
301
# File 'lib/dxopal/image.rb', line 299

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



210
211
212
213
214
215
216
217
218
219
220
# File 'lib/dxopal/image.rb', line 210

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

#load(path_or_url) ⇒ Object



66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
# File 'lib/dxopal/image.rb', line 66

def load(path_or_url)
  raw_img = `new Image()`
  @promise = %x{
    new Promise(function(resolve, reject) {
      raw_img.onload = function() {
        self.$_resize(raw_img.width, raw_img.height);
        self.$_draw_raw_image(0, 0, raw_img);
        self.loaded = #{true};
        resolve();
      };
      raw_img.src = path_or_url;
    });
  }
  return self
end

#loaded?Boolean

Returns:

  • (Boolean)


60
# File 'lib/dxopal/image.rb', line 60

def loaded?; loaded; end

#onload(&block) ⇒ Object



82
83
84
85
86
87
88
# File 'lib/dxopal/image.rb', line 82

def onload(&block)
  %x{
    #{@promise}.then(function(response){
      #{block.call()}
    });
  }
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)



329
330
331
332
333
334
335
336
337
338
339
340
341
342
# File 'lib/dxopal/image.rb', line 329

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



309
310
311
312
313
314
# File 'lib/dxopal/image.rb', line 309

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



317
318
319
320
321
322
323
324
325
# File 'lib/dxopal/image.rb', line 317

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



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

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

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

Draw a filled triangle on this image



285
286
287
288
289
290
291
292
293
294
295
296
# File 'lib/dxopal/image.rb', line 285

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