Отримайте кольори пікселів із полотна на ході миші


85

Чи можна отримати піксель значення RGB під мишею? Чи є повний приклад цього? Ось що я маю на сьогодні:

function draw() {
      var ctx = document.getElementById('canvas').getContext('2d');
      var img = new Image();
      img.src = 'Your URL';

      img.onload = function(){
        ctx.drawImage(img,0,0);


      };

      canvas.onmousemove = function(e) {
            var mouseX, mouseY;

            if(e.offsetX) {
                mouseX = e.offsetX;
                mouseY = e.offsetY;
            }
            else if(e.layerX) {
                mouseX = e.layerX;
                mouseY = e.layerY;
            }
            var c = ctx.getImageData(mouseX, mouseY, 1, 1).data;
            
            $('#ttip').css({'left':mouseX+20, 'top':mouseY+20}).html(c[0]+'-'+c[1]+'-'+c[2]);
      };
    }

Відповіді:


150

Ось повний, самостійний приклад. Спочатку використовуйте такий HTML:

<canvas id="example" width="200" height="60"></canvas>
<div id="status"></div>

Потім нанесіть на полотно кілька квадратів із випадковими кольорами фону:

var example = document.getElementById('example');
var context = example.getContext('2d');
context.fillStyle = randomColor();
context.fillRect(0, 0, 50, 50);
context.fillStyle = randomColor();
context.fillRect(55, 0, 50, 50);
context.fillStyle = randomColor();
context.fillRect(110, 0, 50, 50);

І надрукуйте кожен колір при наведенні курсора миші:

$('#example').mousemove(function(e) {
    var pos = findPos(this);
    var x = e.pageX - pos.x;
    var y = e.pageY - pos.y;
    var coord = "x=" + x + ", y=" + y;
    var c = this.getContext('2d');
    var p = c.getImageData(x, y, 1, 1).data; 
    var hex = "#" + ("000000" + rgbToHex(p[0], p[1], p[2])).slice(-6);
    $('#status').html(coord + "<br>" + hex);
});

Наведений вище код передбачає наявність jQuery та наступних функцій утиліти:

function findPos(obj) {
    var curleft = 0, curtop = 0;
    if (obj.offsetParent) {
        do {
            curleft += obj.offsetLeft;
            curtop += obj.offsetTop;
        } while (obj = obj.offsetParent);
        return { x: curleft, y: curtop };
    }
    return undefined;
}

function rgbToHex(r, g, b) {
    if (r > 255 || g > 255 || b > 255)
        throw "Invalid color component";
    return ((r << 16) | (g << 8) | b).toString(16);
}

function randomInt(max) {
  return Math.floor(Math.random() * max);
}

function randomColor() {
    return `rgb(${randomInt(256)}, ${randomInt(256)}, ${randomInt(256)})`
}

Подивіться це в дії тут:


Чи можете ви, будь ласка, розглянути це питання і побачити, чи є для цього рішення. Я буду дуже вдячний за це :)
Хауер Зешан,

Ваше використання вкладених offsetParents - це дійсно хороший спосіб зробити це. Я ніколи про це не думав. Але чому б вам не використовувати звичайний whileцикл замість a, ifа потім a do...while?
Джонатан Лам

ця відповідь мені допомагає сьогодні (1 вересня 2017 р.). так +1
Жити, щоб померти

@AlivetoDie # 100! Спасибі :)
Уейн,

12

Я знаю, що це старе питання, але тут є альтернатива. Я б зберігав ці дані зображення в масиві, а потім, під час переміщення миші над полотном:

var index = (Math.floor(y) * canvasWidth + Math.floor(x)) * 4
var r = data[index]
var g = data[index + 1]
var b = data[index + 2]
var a = data[index + 3]

Набагато простіше, ніж отримувати imageData щоразу.


7

Об’єднавши різні посилання, знайдені тут у StackOverflow (включаючи статтю вище) та на інших сайтах, я зробив це, використовуючи javascript та JQuery:

<html>
<body>
<canvas id="myCanvas" width="400" height="400" style="border:1px solid #c3c3c3;">
Your browser does not support the canvas element.
</canvas>
<script src="jquery.js"></script>
<script type="text/javascript">
    window.onload = function(){
        var canvas = document.getElementById('myCanvas');
        var context = canvas.getContext('2d');
        var img = new Image();
        img.src = 'photo_apple.jpg';
        context.drawImage(img, 0, 0);
    };

    function findPos(obj){
    var current_left = 0, current_top = 0;
    if (obj.offsetParent){
        do{
            current_left += obj.offsetLeft;
            current_top += obj.offsetTop;
        }while(obj = obj.offsetParent);
        return {x: current_left, y: current_top};
    }
    return undefined;
    }

    function rgbToHex(r, g, b){
    if (r > 255 || g > 255 || b > 255)
        throw "Invalid color component";
    return ((r << 16) | (g << 8) | b).toString(16);
    }

$('#myCanvas').click(function(e){
    var position = findPos(this);
    var x = e.pageX - position.x;
    var y = e.pageY - position.y;
    var coordinate = "x=" + x + ", y=" + y;
    var canvas = this.getContext('2d');
    var p = canvas.getImageData(x, y, 1, 1).data;
    var hex = "#" + ("000000" + rgbToHex(p[0], p[1], p[2])).slice(-6);
    alert("HEX: " + hex);
});
</script>
<img src="photo_apple.jpg"/>
</body>
</html>

Це моє повне рішення. Тут я використовував лише полотно та одне зображення, але якщо вам потрібно використовувати <map>над зображенням, це теж можливо.


5

виклик getImageData кожен раз буде сповільнювати процес ... для прискорення речей я рекомендую зберігати дані зображень, а потім ви можете легко і швидко отримувати значення pix, тому робіть щось подібне для кращої роботи

// keep it global
let imgData = false;  // initially no image data we have

// create some function block 
if(imgData === false){   
  // fetch once canvas data     
  var ctx = canvas.getContext("2d");
  imgData = ctx.getImageData(0, 0, canvas.width, canvas.height);
}
    // Prepare your X Y coordinates which you will be fetching from your mouse loc
    let x = 100;   // 
    let y = 100;
    // locate index of current pixel
    let index = (y * imgData.width + x) * 4;

        let red = imgData.data[index];
        let green = imgData.data[index+1];
        let blue = imgData.data[index+2];
        let alpha = imgData.data[index+3];
   // Output
   console.log('pix x ' + x +' y '+y+ ' index '+index +' COLOR '+red+','+green+','+blue+','+alpha);

Дуже корисно +1
Уейн,

3

Швидка відповідь

context.getImageData(x, y, 1, 1).data;повертає масив rgba. напр[50, 50, 50, 255]


Ось версія функції rgbToHex @ lwburk, яка приймає масив rgba як аргумент.

function rgbToHex(rgb){
  return '#' + ((rgb[0] << 16) | (rgb[1] << 8) | rgb[2]).toString(16);
};

Це працює лише з червоними значеннями вище 16, наприклад, [10, 42, 67, 255]виробляє, #a2a43що не є дійсним / добре відформатованим шістнадцятковим кольором.
Ті Хаусманн

3

Якщо вам потрібно отримати середній колір прямокутної області, а не колір одного пікселя, подивіться на це інше питання:

👉 JavaScript - Отримайте середній колір з певної області зображення

У будь-якому випадку, і те, і інше робиться дуже подібним чином:

Отримання кольору / значення одного пікселя із зображення чи полотна

Щоб отримати колір одного пікселя, ви спочатку намалюєте це зображення на полотні, що ви вже зробили:

const image = document.getElementById('image');
const canvas = document.createElement('canvas');
const context = canvas.getContext('2d');
const width = image.width;
const height = image.height;

canvas.width = width;
canvas.height = height;

context.drawImage(image, 0, 0, width, height);

А потім отримайте значення одного пікселя таким чином:

const data = context.getImageData(X, Y, 1, 1).data;

// RED   = data[0]
// GREEN = data[1]
// BLUE  = data[2]
// ALPHA = data[3]

Пришвидшення скорочується, отримуючи всі ImageData одночасно

Вам потрібно використовувати той самий CanvasRenderingContext2D.getImageData (), щоб отримати значення всього зображення, що ви робите, змінюючи його третій та четвертий параметри. Підпис цієї функції:

ImageData ctx.getImageData(sx, sy, sw, sh);
  • sx: Координата x верхнього лівого кута прямокутника, з якого буде витягнуто ImageData.
  • sy: Координата y верхнього лівого кута прямокутника, з якого буде витягнуто ImageData.
  • sw: Ширина прямокутника, з якого буде витягнуто ImageData.
  • sh: Висота прямокутника, з якого буде витягнуто ImageData.

Ви бачите, що він повертає ImageDataоб’єкт, яким би він не був . Важливим тут є те, що цей об’єкт має .dataвластивість, що містить усі наші піксельні значення.

Однак зауважте, що .dataвластивість є одновимірноюUint8ClampedArray , це означає, що всі компоненти пікселя згладжені, тож ви отримуєте щось таке, що виглядає так:

Скажімо, у вас є зображення 2х2, таке:

 RED PIXEL |       GREEN PIXEL
BLUE PIXEL | TRANSPARENT PIXEL

Тоді ви отримаєте їх так:

[ 255, 0, 0, 255,    0, 255, 0, 255,    0, 0, 255, 255,    0, 0, 0, 0          ]
|   RED PIXEL   |    GREEN PIXEL   |     BLUE PIXEL   |    TRANSPAERENT  PIXEL |
|   1ST PIXEL   |      2ND PIXEL   |      3RD PIXEL   |             4TH  PIXEL | 

Оскільки виклик getImageData- це повільна операція, ви можете зателефонувати їй лише один раз, щоб отримати дані всього зображення ( sw= ширина зображення, sh= висота зображення).

Потім, в наведеному вище прикладі, якщо ви хочете отримати доступ до компонентів TRANSPARENT PIXEL, тобто, один в положенні x = 1, y = 1цього уявного чином, ви знайдете свій перший індекс iв його ImageData«сек dataвласності як:

const i = (y * imageData.width + x) * 4;

✨ Побачимо це в дії

const solidColor = document.getElementById('solidColor');
const alphaColor = document.getElementById('alphaColor');
const solidWeighted = document.getElementById('solidWeighted');

const solidColorCode = document.getElementById('solidColorCode');
const alphaColorCode = document.getElementById('alphaColorCode');
const solidWeightedCOde = document.getElementById('solidWeightedCode');

const brush = document.getElementById('brush');
const image = document.getElementById('image');
const canvas = document.createElement('canvas');
const context = canvas.getContext('2d');
const width = image.width;
const height = image.height;

const BRUSH_SIZE = brush.offsetWidth;
const BRUSH_CENTER = BRUSH_SIZE / 2;
const MIN_X = image.offsetLeft + 4;
const MAX_X = MIN_X + width - 1;
const MIN_Y = image.offsetTop + 4;
const MAX_Y = MIN_Y + height - 1;

canvas.width = width;
canvas.height = height;

context.drawImage(image, 0, 0, width, height);

const imageDataData = context.getImageData(0, 0, width, height).data;

function sampleColor(clientX, clientY) {
  if (clientX < MIN_X || clientX > MAX_X || clientY < MIN_Y || clientY > MAX_Y) {
    requestAnimationFrame(() => {
      brush.style.transform = `translate(${ clientX }px, ${ clientY }px)`;
      solidColorCode.innerText = solidColor.style.background = 'rgb(0, 0, 0)';
      alphaColorCode.innerText = alphaColor.style.background = 'rgba(0, 0, 0, 0.00)';
      solidWeightedCode.innerText = solidWeighted.style.background = 'rgb(0, 0, 0)';
    });
    
    return;
  }
  
  const imageX = clientX - MIN_X;
  const imageY = clientY - MIN_Y;
  
  const i = (imageY * width + imageX) * 4;

  // A single pixel (R, G, B, A) will take 4 positions in the array:
  const R = imageDataData[i];
  const G = imageDataData[i + 1];
  const B = imageDataData[i + 2];
  const A = imageDataData[i + 3] / 255;
  const iA = 1 - A;

  // Alpha-weighted color:
  const wR = (R * A + 255 * iA) | 0;
  const wG = (G * A + 255 * iA) | 0;
  const wB = (B * A + 255 * iA) | 0;

  // Update UI:
  
  requestAnimationFrame(() => {
    brush.style.transform = `translate(${ clientX }px, ${ clientY }px)`;

    solidColorCode.innerText = solidColor.style.background
      = `rgb(${ R }, ${ G }, ${ B })`;

    alphaColorCode.innerText = alphaColor.style.background
      = `rgba(${ R }, ${ G }, ${ B }, ${ A.toFixed(2) })`;

    solidWeightedCode.innerText = solidWeighted.style.background
      = `rgb(${ wR }, ${ wG }, ${ wB })`;
  });
}

document.onmousemove = (e) => sampleColor(e.clientX, e.clientY);
  
sampleColor(MIN_X, MIN_Y);
body {
  margin: 0;
  height: 100vh;
  display: flex;
  flex-direction: row;
  align-items: center;
  justify-content: center;
  cursor: none;
  font-family: monospace;
  overflow: hidden;
}

#image {
  border: 4px solid white;
  border-radius: 2px;
  box-shadow: 0 0 32px 0 rgba(0, 0, 0, .25);
  width: 150px;
  box-sizing: border-box;
}

#brush {
  position: absolute;
  top: 0;
  left: 0;
  pointer-events: none;
  width: 1px;
  height: 1px;
  mix-blend-mode: exclusion;
  border-radius: 100%;
}

#brush::before,
#brush::after {
  content: '';
  position: absolute;
  background: magenta;
}

#brush::before {
  top: -16px;
  left: 0;
  height: 33px;
  width: 100%;
}

#brush::after {
  left: -16px;
  top: 0;
  width: 33px;
  height: 100%;
}

#samples {
  position: relative;
  list-style: none;
  padding: 0;
  width: 250px;
}

#samples::before {
  content: '';
  position: absolute;
  top: 0;
  left: 27px;
  width: 2px;
  height: 100%;
  background: black;
  border-radius: 1px;
}

#samples > li {
  position: relative;
  display: flex;
  flex-direction: column;
  justify-content: center;
  padding-left: 56px;
}

#samples > li + li {
  margin-top: 8px;
}

.sample {
  position: absolute;
  top: 50%;
  left: 16px;
  transform: translate(0, -50%);
  display: block;
  width: 24px;
  height: 24px;
  border-radius: 100%;
  box-shadow: 0 0 16px 4px rgba(0, 0, 0, .25);  
  margin-right: 8px;
}

.sampleLabel {
  font-weight: bold;
  margin-bottom: 8px;
}

.sampleCode {
  
}
<img id="image" src="data:image/gif;base64,R0lGODlhSwBLAPEAACMfIO0cJAAAAAAAACH/C0ltYWdlTWFnaWNrDWdhbW1hPTAuNDU0NTUAIf4jUmVzaXplZCBvbiBodHRwczovL2V6Z2lmLmNvbS9yZXNpemUAIfkEBQAAAgAsAAAAAEsASwAAAv+Uj6mb4A+QY7TaKxvch+MPKpC0eeUUptdomOzJqnLUvnFcl7J6Pzn9I+l2IdfII8DZiCnYsYdK4qRTptAZwQKRVK71CusOgx2nFRrlhMu+33o2NEalC6S9zQvfi3Mlnm9WxeQ396F2+HcQsMjYGEBRVbhy5yOp6OgIeVIHpEnZyYCZ6cklKBJX+Kgg2riqKoayOWl2+VrLmtDqBptIOjZ6K4qAeSrL8PcmHExsgMs2dpyIxPpKvdhM/YxaTMW2PGr9GP76BN3VHTMurh7eoU14jsc+P845Vn6OTb/P/I68iYOfwGv+JOmRNHBfsV5ujA1LqM4eKDoNvXyDqItTxYX/DC9irKBlIhkKGPtFw1JDiMeS7CqWqySPZcKGHH/JHGgIpb6bCl1O0LmT57yCOqoI5UcU0YKjPXmFjMm0ZQ4NIVdGBdZRi9WrjLxJNMY1Yr4dYeuNxWApl1ALHb+KDHrTV1owlriedJgSr4Cybu/9dFiWYAagsqAGVkkzaZTAuqD9ywKWMUG9dCO3u2zWpVzIhpW122utZlrHnTN+Bq2Mqrlnqh8CQ+0Mrq3Kc++q7eo6dlB3rLuh3abPVbbbI2mxBdhWdsZhid8cr0oy9F08q0k5FXSadiyL1mF5z51a8VsQOp3/LlodkBfzmzWf2bOrtfzr48k/1hupDaLa9rUbO+zlwndfaOCURAXRNaCBqBT2BncJakWfTzSYkmCEFr60RX0V8sKaHOltCBJ1tAAFYhHaVVbig3jxp0IBADs=" >

<div id="brush"></div>

<ul id="samples">
  <li>
    <span class="sample" id="solidColor"></span>
    <div class="sampleLabel">solidColor</div>
    <div class="sampleCode" id="solidColorCode">rgb(0, 0, 0)</div>
  </li>
  <li>
    <span class="sample" id="alphaColor"></span>
    <div class="sampleLabel">alphaColor</div>
    <div class="sampleCode" id="alphaColorCode">rgba(0, 0, 0, 0.00)</div>
  </li>
  <li>
    <span class="sample" id="solidWeighted"></span>
    <div class="sampleLabel">solidWeighted (with white)</div>
    <div class="sampleCode" id="solidWeightedCode">rgb(0, 0, 0)</div>
  </li>
</ul>

Зверніть увагу: я використовую невеликий URI даних, щоб уникнути Cross-Origin проблем, якщо я зовнішнє зображення або відповідь, яка перевищує допустиму, якщо я намагаюся використовувати довший URI даних.

🕵️ Ці кольори виглядають дивно, чи не так?

Якщо навести курсор навколо меж фігури зірочки, ви побачите, що інколи avgSolidColorчервоний, але піксель, який вибираєте, виглядає білим. Це тому, що, хоча Rкомпонент для цього пікселя може бути високим, альфа-канал низький, тому колір насправді є майже прозорим червоним відтінком, але avgSolidColorігнорує це.

З іншого боку, avgAlphaColorвиглядає рожевим. Ну, це насправді неправда, це просто виглядає рожевим, тому що зараз ми використовуємо альфа-канал, що робить його напівпрозорим і дозволяє побачити фон сторінки, який у цьому випадку є білим.

Color Альфа-зважений колір

Тоді, що ми можемо зробити, щоб це виправити? Ну, виявляється, нам просто потрібно використовувати альфа-канал та його обернене значення як ваги для обчислення компонентів нашої нової вибірки, в даному випадку об’єднавши його з білим, оскільки це колір, який ми використовуємо як фон.

Це означає, що якщо піксель знаходиться R, G, B, A, де Aзнаходиться інтервал [0, 1], ми обчислимо обернену до альфа-каналу iA, а компоненти зваженої вибірки як:

const iA = 1 - A;
const wR = (R * A + 255 * iA) | 0;
const wG = (G * A + 255 * iA) | 0;
const wB = (B * A + 255 * iA) | 0;

Зверніть увагу, що чим прозоріший піксель ( Aближче до 0), тим світліший колір.



1

У мене є дуже простий робочий приклад отримання кольору пікселів із полотна.

Спочатку кілька основних HTML:

<canvas id="myCanvas" width="400" height="250" style="background:red;" onmouseover="echoColor(event)">
</canvas>

Потім JS намалювати щось на полотні та отримати колір:

var c = document.getElementById("myCanvas");
var ctx = c.getContext("2d");
ctx.fillStyle = "black";
ctx.fillRect(10, 10, 50, 50);

function echoColor(e){
    var imgData = ctx.getImageData(e.pageX, e.pageX, 1, 1);
    red = imgData.data[0];
    green = imgData.data[1];
    blue = imgData.data[2];
    alpha = imgData.data[3];
    console.log(red + " " + green + " " + blue + " " + alpha);  
}

Ось робочий приклад , просто подивіться на консоль.


0

@Wayne Burkett's відповідь хороший. Якщо ви хочете також витягти значення альфа, щоб отримати колір rgba, ми могли б зробити це:

var r = p[0], g = p[1], b = p[2], a = p[3] / 255;
var rgba = "rgb(" + r + "," + g + "," + b + "," + a + ")";

Я розділив значення альфа на 255, оскільки об'єкт ImageData зберігає його як ціле число від 0 до 255, але для більшості програм (наприклад, CanvasRenderingContext2D.fillRect()) потрібні кольори у дійсному форматі CSS, де значення альфа становить від 0 до 1.

(Також пам’ятайте, що якщо ви витягнете прозорий колір, а потім намалюєте його назад на полотно, він накладе будь-який колір, що був раніше. Отже, якщо ви намалювали колір rgba(0,0,0,0.1)на тому самому місці 10 разів, він буде чорним.)

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.