Is it possible to iterate each pixel of a bitmap image? Eventually what I'm trying to achieve is that I need to get the coordinate values of each pixel of a bitmap image and change the color of those pixels according to their coordinate values. As I see it, I need to use the getPixels() method but I still did not understand exactly what I should do.
( too slow :) )
so this is the sae as above with a linear loop instead of 2 nested loops.
//creates a new BitmapData, with transparency, white 0xFFFFFF
var bd:BitmapData = new BitmapData( 100, 100, false, 0xFFFFFF );
//stores the width and height of the image
var w:int = bd.width;
var h:int = bd.height;
var i:int = w * h;
var x:int, y:int, col;
//decremental loop are said to be faster :)
while ( i-- )
{
//this is the position of each pixel in x & y
x = i % w;
y = int( i / w );
//gets the current color of the pixel ( 0xFFFFFF )
col = bd.getPixel( x, y );
//assign the 0xFF0000 ( red ) color to the pixel
bd.setPixel( x, y, 0xFF0000 );
}
addChild( new Bitmap( bd ) );//a nice red block
note that if you're using a bitmapData with an alpha channel (say if you load the image, the alpha will be turned on automatically ) you 'll have to use
bd.getPixel32( x, y );// returns a uint : 0xFF000000
//and
bd.setPixel32( x, y, UINT );// 0xFF000000
EDIT : I 've done a quick bench :
package
{
import flash.display.BitmapData;
import flash.display.Sprite;
import flash.utils.getTimer;
public class pixels extends Sprite
{
private var bd:BitmapData = new BitmapData( 100, 100, false, 0xFFFFFF );
public function pixels()
{
var i:int, total:int = 100, t:int = 0;
t = getTimer();
i = total;
while( i-- )
{
whileLoop( bd );
}
trace( 'while:', getTimer() - t );
t = getTimer();
i = total;
while( i-- )
{
forLoop( bd );
}
trace( 'for:', getTimer() - t );
}
private function forLoop( bd:BitmapData ):void
{
var i:int, j:int;
var col:int;
for ( i = 0; i < bd.width; i++ )
{
for ( j = 0; j < bd.height; j++ )
{
col = bd.getPixel( i, j ); // +/- 790 ms
}
}
//for ( i = 0; i < bd.width; i++ ) for ( j = 0; j < bd.height; j++ ) col = bd.getPixel( i, j ); // +/-530 ms
//var w:int = bd.width;
//var h:int = bd.height;
//for ( i = 0; i < w; i++ ) for ( j = 0; j < h; j++ ) col = bd.getPixel( i, j ); // +/-250 ms
}
private function whileLoop( bd:BitmapData ):void
{
var w:int = bd.width;
var h:int = bd.height;
var i:int = w * h;
var col:int;
while ( i-- )
{
col = bd.getPixel( i % w, int( i / w ) ); // +/- 580 ms
}
//while ( i-- ) col = bd.getPixel( i % w, int( i / w ) ); // +/- 330 ms
}
}
}
for 100 * ( 100 * 100 ) getPixel, the fastest (on my machine) is the one-line for loop with local variables. ( +/- 250 ms ) then the one-line while( +/- 330 ms ) :)
storing local variables w and h for width and height makes the for loops twice faster :)
good to know
If, as you say, you are only setting the pixels based on their x and y, you need neither getPixel() nor getPixels()!
myBitmapData.lock();
for( var j:int = 0; j < myBitmapData.height; j++ )
{
for( var i:int = 0; i < myBitmapData.width; i++ )
{
var alpha:uint = 0xFF000000; // Alpha is always 100%
var red:uint = 0x00FF0000 * ( i / myBitmapData.width ); // Set red based on x
var green:uint = 0x0000FF00 * ( j / myBitmapData.height ); // Set green based on y
var newColor:uint = alpha + red + green; // Add the components
// Set the new pixel value (setPixel32() includes alpha, e.g. 0xFFFF0000 => alpha=FF, red=FF, green=00, blue=00)
myBitmapData.setPixel32( i, j, newColor );
}
}
myBitmapData.unlock();
If, however, you want to read the pixels' current value, let me join the speed competition.
In addition to earlier answers, here's much more speed increase!
Instead of numerous calls to getPixel(), you can use getPixels() to get a byteArray of the pixel data.
myBitmapData.lock();
var numPixels:int = myBitmapData.width * myBitmapData.height;
var pixels:ByteArray = myBitmapData.getPixels( new Rectangle( 0, 0, myBitmapData.width, myBitmapData.height ) );
for( var i:int = 0; i < numPixels; i++ )
{
// Read the color data
var color:uint = pixels.readUnsignedInt();
// Change it if you like
// Write it to the pixel (setPixel32() includes alpha, e.g. 0xFFFF0000 => alpha=FF, red=FF, green=00, blue=00)
var theX:int = i % myBitmapData.width;
myBitmapData.setPixel32( theX, ( i - theX ) / myBitmapData.width, color );
}
myBitmapData.unlock();
You need a BitmapData object. Then, it's a simple straight-forward nested loop :
var pix : int; //AS3 uses int even for uint types
for (var x:int = 0; x < myBitmapData.width; x++)
{
for (var y:int = 0; y < myBitmapData.height; y++)
{
// This'll get you the pixel color as RGB
pix = myBitmapData.getPixel(x,y);
// To change the color, use the setPixel method + the uint corresponding
// to the new color.
}
}
Related
I am trying to get each average color of 100 rectangle as pictures
enter image description here
As the picture show, the pasted imageData doesn't set suitably. But the all coordinate parameter of (ctx.getImageData() and ctx.putImageData()) is same as console.log picture I attached
enter image description here
Is it bug? Or did I miss something ?
convert(){
let canvas = this.$el.querySelector('#pixel-art-canvas'),
image = this.$el.querySelector('#upload-image'),
ctx = canvas.getContext('2d'),
degree = 10,
img = new Image,
tiles = Math.pow(degree,2),
eachWidth,eachHeight;
img.src = image.src;
ctx.drawImage(img,0,0);
eachWidth= canvas.width/degree;
eachHeight= canvas.height/degree;
for(let k = 0; k < tiles; k++) {
let imgd,x,y,
rgb = {r:0,g:0,b:0},
count = 0;
x = (k % degree) * eachWidth;
y = (k / degree) * eachHeight;
imgd = ctx.getImageData(x, y, eachWidth, eachHeight);
console.log('x: ' + x + ' , y:' +y+' , w: '+eachWidth + ' , h :' +eachHeight);
for (let i=0; i < imgd.data.length; i=i+4) {
rgb.r += imgd.data[i];
rgb.g += imgd.data[i+1];
rgb.b += imgd.data[i+2];
count++;
}
rgb.r = ~~(rgb.r/count);
rgb.g = ~~(rgb.g/count);
rgb.b = ~~(rgb.b/count);
for (let j=0; j < imgd.data.length; j=j+4) {
imgd.data[j] = rgb.r;
imgd.data[j+1] = rgb.g;
imgd.data[j+2] = rgb.b;
}
ctx.putImageData(imgd, x, y, 0, 0, eachWidth, eachHeight);
console.log('x: ' + x + ' , y:' +y+' , w: '+eachWidth + ' , h :' +eachHeight);
}//end for
}
The problem is that you are incorrectly calculating the y coordinate.
You have
y = (k / degree) * eachHeight;
Which will give a fractional result for (k/degree) you need to round (floor) the value before multiplying it.
// to fix the y coord.
y = Math.floor(k / degree) * eachHeight;
// or
y = ((k / degree) | 0) * eachHeight;
Also you are incorrectly getting the mean of the colors. RGB values represent the square root of the intensity of the pixel. Thus the difference in intensity between a RGB value of 128 and 256 is not 2 times but 4 times as bright.
If you take the mean by just summing the RGB values you end up with a result that is darker than it should be.
The correct method is to get the mean of the the square of the RGB values, then convert back to logarithmic RGB to put back onto the canvas.
Change you code
const rgb = {r:0,g:0,b:0};
var count = 0;
for (let i=0; i < imgd.data.length; i=i+4) {
rgb.r += imgd.data[i] * imgd.data[i]; // Square the value
rgb.g += imgd.data[i + 1] * imgd.data[i + 1];
rgb.b += imgd.data[i + 2] * imgd.data[i + 1];
count++;
}
// Get mean and convert back to logarithmic
// Also you do not need to floor the values with ~~(val) as
// the array is of type Uint8ClampedArray which will floor and clamp the
// values for you.
// Also ~~(val) requires 2 operations, a quicker way the requires only one
// operation is (val) | 0
rgb.r = Math.sqrt(rgb.r/count);
rgb.g = Math.sqrt(rgb.g/count);
rgb.b = Math.sqrt(rgb.b/count);
for (let j=0; j < imgd.data.length; j=j+4) {
imgd.data[j] = rgb.r;
imgd.data[j+1] = rgb.g;
imgd.data[j+2] = rgb.b;
}
ctx.putImageData(imgd, x, y, 0, 0, eachWidth, eachHeight);
You can also optimise a little via.
const x = (k % degree) * eachWidth;
const y = ((k / degree) | 0) * eachHeight;
const imgd = ctx.getImageData(x, y, eachWidth, eachHeight);
const rgb = {r : 0, g : 0, b : 0};
const count = imgd.data.length / 4;
var i = 0;
while( i < imgd.data.length ) {
rgb.r += imgd.data[i] * imgd.data[i++]; // square the value
rgb.g += imgd.data[i] * imgd.data[i++];
rgb.b += imgd.data[i] * imgd.data[i++];
i ++;
}
// need to round as we are not directly adding back to the buffer.
rgb.r = Math.sqrt(rgb.r / count) | 0;
rgb.g = Math.sqrt(rgb.g / count) | 0;
rgb.b = Math.sqrt(rgb.b / count) | 0;
// get a refer to 32 bit version of same data and set all values
// the 0xFF000000 sets the alpha to 255
// shift red 2 bytes (16 bits)
// shift green 1 byte (8 bit)
// blue is in the correct place.
new Uint32Array(imgd.data.buffer).fill(
0xFF000000 + (rgb.r << 16) + (rgb.g << 8) + rgb.b
);
// putImageData use 3 arguments imgd and the x, y if you are
// copying all the data back to the canvas.
ctx.putImageData(imgd, x, y);
In Flash AS3, how would I write a function that will:
Take in an integer (a list index, for example)
return a visually distinct hex color based on that number (and will consistently return that same color given that same number)
The purpose is to provide a visually distinct color for each item in varying-length list of items. The most I expect to support is around 200, but I don't see the count going far above 20 or so for most.
Here's my quick and dirty:
public static function GetAColor(idx:int):uint {
var baseColors:Array = [0xff0000, 0x00ff00, 0xff0080, 0x0000ff, 0xff00ff, 0x00ffff, 0xff8000];
return Math.round(baseColors[idx % baseColors.length] / (idx + 1) * 2);
}
It does OK, but it would be nice to see a more distinct set of colors that are not so visually close to one another
You could go with generator of random values that supports seed, so you will be able return same color. As for color you could build it - by randomValue * 0xFFFFFF, where randomValue between 0 and 1. And exclude values (colors) that are close.
Second option: build palette of 200 colors with step - 0xFFFFFF / 200 and shuffle palette with predefined logic, so you will have same colors.
Third option: as for really distinct colors, you could go with big jumps in every channel. Example: 0xFF * 0.2 - 5 steps in every channel.
Fourth option: go with HSV. It's easy to understand(watch image, rotate hue from 0 to 360, change saturation and value from 0 to 100) how to manipulate parameters to get distinct color:
//Very simple demo, where I'm only rotating Hue
var step:uint = 15;
var position:uint = 0;
var colors:Array = [];
for (; position < 360; position += step) {
colors.push(HSVtoRGB(position, 100, 100));
}
//Visualisation for demo
var i:uint, len:uint = colors.length, size:uint = 40, shape:Shape, posX:uint, posY:uint;
for (i; i < len; ++i) {
shape = new Shape();
shape.graphics.beginFill(colors[i]);
shape.graphics.drawRect(0, 0, size, size);
addChild(shape);
shape.x = posX;
shape.y = posY;
posX += size;
if (posX + size >= stage.stageWidth) {
posX = 0;
posY += size;
}
}
public function HSVtoRGB(h:Number, s:Number, v:Number):uint {
var r:Number = 0;
var g:Number = 0;
var b:Number = 0;
var tempS:Number = s / 100;
var tempV:Number = v / 100;
var hi:int = Math.floor(h / 60) % 6;
var f:Number = h / 60 - Math.floor(h / 60);
var p:Number = (tempV * (1 - tempS));
var q:Number = (tempV * (1 - f * tempS));
var t:Number = (tempV * (1 - (1 - f) * tempS));
switch (hi) {
case 0:
r = tempV;
g = t;
b = p;
break;
case 1:
r = q;
g = tempV;
b = p;
break;
case 2:
r = p;
g = tempV;
b = t;
break;
case 3:
r = p;
g = q;
b = tempV;
break;
case 4:
r = t;
g = p;
b = tempV;
break;
case 5:
r = tempV;
g = p;
b = q;
break;
}
return (Math.round(r * 255) << 16 | Math.round(g * 255) << 8 | Math.round(b * 255));
}
And last one, if you want go with this task like a pro, this wiki article could be helpful for you.
Is there any way to devide by 10 and to check if the result is a float number?
My vars is:
var X:int=40;
var Y:Number=0;
//I want to Y get 4,
//but when X is 45, Y get 0
if( X%10 == 0 )
Y = X/10;
else
Y = 0;
Try this:
var X:int = 45;
var Y:Number = (X / 10).toString().indexOf(".") == -1 ? X / 10 : 0;
Longer form:
var X:int = 45;
var Y:Number = 0;
var Z:String = (X / 10).toString();
if (Z.indexOf(".") == -1) {
Y = X / 10;
} else {
Y = 0;
}
I was wondering if someone could help me with a circle meter gauage i have taken some code from a different example and i am just protypting stuff to see if i can get it to work here is a working example.
http://jsbin.com/ixuyid/28/edit
Click run with javascript
Code below
var context;
canvas = document.getElementById('myCanvas');
context = canvas.getContext('2d');
//use a reusable function
function drawCircle(num){
console.log(num);
var x = canvas.width / 2;
var y = canvas.height / 2;
var radius = 75;
var startAngle = 0 * Math.PI;
var endAngle = num * Math.PI;
var counterClockwise = false;
context.beginPath();
context.arc(x, y, radius, startAngle, endAngle, counterClockwise);
context.lineWidth = 5;
// line color
context.strokeStyle = 'black';
context.stroke();
}
drawCircle();
var num = 1;
setInterval(function(){
},1000);
+function(){
var ctx = new webkitAudioContext()
, url = '//kevincennis.com/sound/loudpipes.mp3'
, audio = new Audio(url)
// 2048 sample buffer, 1 channel in, 1 channel out
, processor = ctx.createJavaScriptNode(2048, 1, 1)
, meter = document.getElementById('meter')
, source
audio.addEventListener('canplaythrough', function(){
source = ctx.createMediaElementSource(audio)
source.connect(processor)
source.connect(ctx.destination)
processor.connect(ctx.destination)
audio.play()
}, false);
// loop through PCM data and calculate average
// volume for a given 2048 sample buffer
processor.onaudioprocess = function(evt){
var input = evt.inputBuffer.getChannelData(0)
, len = input.length
, total = i = 0
, rms
while ( i < len ) total += Math.abs( input[i++] )
rms = Math.sqrt( total / len )
meter.style.width = ( rms * 100 ) + '%';
context.clearRect(100,50,200,200);
drawCircle(rms);
}
}()
I seem to be having issue with the levels???
Any help
Change these two lines in the drawCircle function:
var startAngle = 0; //multiplying with 0 will result in 0
var endAngle = 360 * num * Math.PI / 180;
Your num seem to be a value between 0 and 1 so we need to add what we're using that with, here 360 degrees, then convert by using PI / 180.
The other problem is that the clearRect wasn't extended far enough so it left part of the arc uncleared to the right.
Tip: To make it look more realistic you can update your rms only when the new rms is higher, and if not just subtract a small value for each frame.
For example:
//global scope
var oldRMS = 0;
Inside your processor.onaudioprocess after vars:
if (rms > oldRMS) oldRMS = rms;
meter.style.width = ( oldRMS * 100 ) + '%';
context.clearRect(100,50,canvas.width,canvas.height);
drawCircle(oldRMS);
oldRMS -= 0.04; //speed of fallback
Modifcations:
http://jsbin.com/ixuyid/29/edit
I took the example of Laplace from "Making image filters with Canvas", but I can not understand the use of Math.min() function in the following lines. Can anyone explain to me how the Laplace?
var weights = [-1,-1,-1,
-1, 8,-1,
-1,-1,-1];
var opaque = true;
var side = Math.round(Math.sqrt(weights.length));
var halfSide = Math.floor(side/2);
var imgd = context.getImageData(0, 0, canvas.width, canvas.height);
var src = imgd.data;
var sw = canvas.width;
var sh = canvas.height;
var w = sw;
var h = sh;
var output = contextNew.createImageData(w, h);
var dst = output.data;
var alphaFac = opaque ? 1 : 0;
for (var y=0; y<h; y++) {
for (var x=0; x<w; x++) {
var sy = y;
var sx = x;
var dstOff = (y*w+x)*4;
var r=0, g=0, b=0, a=0;
for (var cy=0; cy<side; cy++) {
for (var cx=0; cx<side; cx++) {
var scy = Math.min(sh-1, Math.max(0, sy + cy - halfSide));
var scx = Math.min(sw-1, Math.max(0, sx + cx - halfSide));
var srcOff = (scy*sw+scx)*4;
var wt = weights[cy*side+cx];
r += src[srcOff] * wt;
g += src[srcOff+1] * wt;
b += src[srcOff+2] * wt;
a += src[srcOff+3] * wt;
}
}
dst[dstOff] = r;
dst[dstOff+1] = g;
dst[dstOff+2] = b;
dst[dstOff+3] = a + alphaFac*(255-a);
}
}
its algorithm is something like
for y = 0 to imageHeight
for x = 0 to imageWidth
sum = 0
for i = -h to h
for j = -w to w
sum = sum + k(j, i) * f(x – j, y – i)
end for j
end for i
g(x, y) = sum end for x end for y