ホームページ  >  記事  >  ウェブフロントエンド  >  単色の QR コードをカラーの QR コードに変換する方法 (JavaScript)

単色の QR コードをカラーの QR コードに変換する方法 (JavaScript)

零下一度
零下一度オリジナル
2017-04-28 10:21:483788ブラウズ

この記事では主にJavaScriptのソリッドカラーQRコードをカラーQRコードに変換する解決策を詳しく紹介しますので、興味のある方は参考にしてください

この記事では主にJavaScriptのQRコードをカラーQRコードに変換する方法について説明します。単色の QR コードが色付きになります。

少し前に、顧客は背景によって生成された単色の QR コードが気に入らず、色付きの QR コードを求めていました。そこで、このタスクは画像処理であるため、Canvas を使用してピクセル操作を実行することが主な目的でした。そのため、いくつかの試みを行いましたが、いくつかの落とし穴に遭遇しました。具体的な記録は次のとおりです。

前提知識

drawImageメソッドはキャンバス上に絵を描くことができ、getImageDataメソッドは長方形領域内のすべてのピクセルの情報を取得することができ、戻り値のdata属性はone-次元配列 には、すべてのピクセルの情報が格納されます。1 つのピクセルの情報は、それぞれ r、g、b、透明度を表す 4 つの要素を占めます。 1 次元配列内のピクセルの順序は、左から右、上から下です。最後は putImageData メソッドで、変更されたピクセル情報配列をキャンバスに返します。

いくつかの小さな落とし穴

1 つ目の落とし穴は、キャンバスが CSS を使用する代わりに属性を使用して幅と高さを指定することです

2 つ目の落とし穴は、画像処理にはサーバー環境が必要であるように見えることです。ローカルで可能だと聞きましたが、セキュリティ上の考慮事項に基づいて、ローカルサーバーをセットアップすることで最終的にキャンバスエラーを解決しました。

3番目のピットはスタックオーバーフローです。これについては、後で詳しく説明します

主なアイデアは「ああ!」から来ています。アルゴリズム! 「深さ優先検索と幅優先検索の章では、章の最後の部分「宝島の冒険」で、異なるエリアに順番に番号を付ける実装が行われています。番号は色分けのようなものだと考えられていますが、実際には同じです。

具体的な実装

実際、いわゆるカラーQRコードは、各ピクセルの色がランダムな種類のQRコードではありません。 QRコードをよく見てみると、海の中に点在する島のように、黒い部分が一つ一つ、白い部分の中に分布していることが分かります。個別にブロックします。黒ブロックの本質は黒ピクセルです。

前に述べたように、キャンバスはピクセル操作を実行できるので使用します。そのため、実際の操作は明らかに背景色を染めたくないため、前述したように背景色を判断する必要があります。背景色は、黒い色のブロックを区切る海のようなものです。つまり、ピクセルを読み込んで染色した後、その右側のピクセルの色を常に判断します。境界に到達したため、正しい方向の色付けを停止できますが、実際には各ピクセルに 4 つの方向が接続されています。ピクセルの右側が背景色である場合、これは深さ優先探索です。再帰により、現在のピクセルの次の位置の色が背景色であることを継続的に確認し、背景色でない場合は戻って別の方向を試し、染色したピクセルを 4 つ検証します。方向。

背景色かどうかを判断するには、RGBA 値を比較する必要があるため、もう 1 つはピクセル情報の配列です。したがって、正しいピクセル情報を比較する場合は、この部分も処理する必要があります。

少しわかりにくいかもしれません、コードを見てみましょう


パート1

、キャンバス

// canvas 部分
var canvas = $("canvas")[0];
var ctx = canvas.getContext("2d");

var img = new Image();
img.src = path; //这里的path就是图片的地址
パート2、

色処理

// 分离颜色参数  返回一个数组
var colorRgb = (function() {
  var reg = /^#([0-9a-fA-f]{3}|[0-9a-fA-f]{6})$/;

  return function(str) {
    var sColor = str.toLowerCase();
    if (sColor && reg.test(sColor)) {
      if (sColor.length === 4) {
        var sColorNew = "#";
        for (var i = 1; i < 4; i += 1) {
          sColorNew += sColor.slice(i, i + 1).concat(sColor.slice(i, i + 1));
        }
        sColor = sColorNew;
      }
      //处理六位的颜色值 
      var sColorChange = [];
      for (var i = 1; i < 7; i += 2) {
        sColorChange.push(parseInt("0x" + sColor.slice(i, i + 2)));
      }
      return sColorChange;
    } else {
      var sColorChange = sColor.replace(/(rgb\()|(\))/g, "").split(",").map(function(a) {
        return parseInt(a);
      });
      return sColorChange;
    }
  }
})();
パート3、

初期パラメータを与える

冗長な操作では、マーク配列を使用して判定された位置を記録します

// 参数
var bg = colorRgb("#fff"); //忽略的背景色
var width = 220;
var height = 220;
var imgD; //预留给 像素信息
var colors = ["#368BFF", "#EF2767", "#F17900", "#399690", "#5aa6f7", "#fd417e", "#ffc000", "#59b6a6"];  //染色数组
// 随机colors数组的一个序号
var ranNum = (function() {
  var len = colors.length;
  return function() {
    return Math.floor(Math.random() * len);
  }
})();

// 标记数组 
var book = []; 
for (var i = 0; i < height; i++) { 
  book[i] = []; 
  for (var j = 0; j < width; j++) { 
    book[i][j] = 0; 
  } 
}

パート 4、

ピクセル情報を取得し、各ピクセルを走査し、最後にそれをキャンバスに戻します

マークされている場合はスキップします。マークされていない場合は、ランダムな色、深さ優先検索、および色付けを使用します

img.onload = function() {
  ctx.drawImage(img, 0, 0, width, height);
  imgD = ctx.getImageData(0, 0, width, height);

  for (var i = 0; i < height; i++) {
    for (var j = 0; j < width; j++) {
      if (book[i][j] == 0 && checkColor(i, j, width, bg)) { //没标记过 且是非背景色
        book[i][j] = 1;
        var color = colorRgb(colors[ranNum()]);
        dfs(i, j, color);  //深度优先搜索
      }
    }
  }

  ctx.putImageData(imgD, 0, 0);
}
// 验证该位置的像素 不是背景色为true
function checkColor(i, j, width, bg) {
  var x = calc(width, i, j);

  if (imgD.data[x] != bg[0] && imgD.data[x + 1] != bg[1] && imgD.data[x + 2] != bg[2]) {
    return true;
  } else {
    return false;
  }
}

// 改变颜色值
function changeColor(i, j, colorArr) {
  var x = calc(width, i, j);
  imgD.data[x] = colorArr[0];
  imgD.data[x + 1] = colorArr[1];
  imgD.data[x + 2] = colorArr[2];
}


// 返回对应像素点的序号
function calc(width, i, j) {
  if (j < 0) {
    j = 0;
  }
  return 4 * (i * width + j);
}

キーコード

操作を簡素化するために方向配列を使用します。試行する方向は時計回りであることに同意しました。右から。

// 方向数组
var next = [
  [0, 1], //右
  [1, 0], //下
  [0, -1], // 左
  [-1, 0] //上 
];

// 深度优先搜索 
function dfs(x, y, color) {
  changeColor(x, y, color);
  for (var k = 0; k <= 3; k++) {
    // 下一个坐标
    var tx = x + next[k][0];
    var ty = y + next[k][1];

    //判断越界
    if (tx < 0 || tx >= height || ty < 0 || ty >= width) {
      continue;
    }


    if (book[tx][ty] == 0 && checkColor(tx, ty, width, bg)) {
      // 判断位置
      book[tx][ty] = 1;
      dfs(tx, ty, color);
    }

  }
  return;
}

最後に遭遇した落とし穴は、長さと幅が220を超えるとスタックがオーバーフローするということですが、これより小さい場合は問題ありません。具体的な理由は不明です。そこに問題があり、無限ループが発生している可能性があります。

すべてのコードはここにあります

// 分离颜色参数  返回一个数组
var colorRgb = (function() {
  var reg = /^#([0-9a-fA-f]{3}|[0-9a-fA-f]{6})$/;

  return function(str) {
    var sColor = str.toLowerCase();
    if (sColor && reg.test(sColor)) {
      if (sColor.length === 4) {
        var sColorNew = "#";
        for (var i = 1; i < 4; i += 1) {
          sColorNew += sColor.slice(i, i + 1).concat(sColor.slice(i, i + 1));
        }
        sColor = sColorNew;
      }
      //处理六位的颜色值 
      var sColorChange = [];
      for (var i = 1; i < 7; i += 2) {
        sColorChange.push(parseInt("0x" + sColor.slice(i, i + 2)));
      }
      return sColorChange;
    } else {
      var sColorChange = sColor.replace(/(rgb\()|(\))/g, "").split(",").map(function(a) {
        return parseInt(a);
      });
      return sColorChange;
    }
  }
})();

// 验证该位置的像素 不是背景色为true
function checkColor(i, j, width, bg) {
  var x = calc(width, i, j);

  if (imgD.data[x] != bg[0] && imgD.data[x + 1] != bg[1] && imgD.data[x + 2] != bg[2]) {
    return true;
  } else {
    return false;
  }
}

// 改变颜色值
function changeColor(i, j, colorArr) {
  var x = calc(width, i, j);
  imgD.data[x] = colorArr[0];
  imgD.data[x + 1] = colorArr[1];
  imgD.data[x + 2] = colorArr[2];
}

// 返回对应像素点的序号
function calc(width, i, j) {
  if (j < 0) {
    j = 0;
  }
  return 4 * (i * width + j);
}
// 方向数组
var next = [
  [0, 1], //右
  [1, 0], //下
  [0, -1], // 左
  [-1, 0] //上 
];

// 深度优先搜索 
function dfs(x, y, color) {
  changeColor(x, y, color);
  for (var k = 0; k <= 3; k++) {
    // 下一个坐标
    var tx = x + next[k][0];
    var ty = y + next[k][1];

    //判断越界
    if (tx < 0 || tx >= height || ty < 0 || ty >= width) {
      continue;
    }

    if (book[tx][ty] == 0 && checkColor(tx, ty, width, bg)) {
      // 判断位置
      book[tx][ty] = 1;
      dfs(tx, ty, color);
    }

  }
  return;
}

/*****上面为封装的函数*****/

/***参数***/
var bg = colorRgb("#fff"); //忽略的背景色
var width = 220;
var height = 220;
var imgD; //预留给 像素信息数组
var colors = ["#368BFF", "#EF2767", "#F17900", "#399690", "#5aa6f7", "#fd417e", "#ffc000", "#59b6a6"];  //染色数组
// 随机colors数组的一个序号
var ranNum = (function() {
  var len = colors.length;
  return function() {
    return Math.floor(Math.random() * len);
  }
})();

// 标记数组 
var book = []; 
for (var i = 0; i < height; i++) { 
  book[i] = []; 
  for (var j = 0; j < width; j++) { 
    book[i][j] = 0; 
  } 
}
// canvas 部分
var canvas = $("canvas")[0];
var ctx = canvas.getContext("2d");

var img = new Image();
img.src = path; //这里的path就是图片的地址
img.onload = function() {
  ctx.drawImage(img, 0, 0, width, height);
  imgD = ctx.getImageData(0, 0, width, height);

  for (var i = 0; i < height; i++) {
    for (var j = 0; j < width; j++) {
      if (book[i][j] == 0 && checkColor(i, j, width, bg)) { //没标记过 且是非背景色
        book[i][j] = 1;
        var color = colorRgb(colors[ranNum()]);
        dfs(i, j, color);  //深度优先搜索
      }
    }
  }

  ctx.putImageData(imgD, 0, 0);
}

概要

少し長く感じますが、ほとんどの関数は実際にピクセル情報を処理します。これを実装するには、深さ優先検索を理解することが重要です。染色されたピクセルは自然にマークされるため、新しいマークされていないピクセルが表示されると、それは当然新しいカラー ブロックを意味します。細かいところでは、imgD.dataとピクセルシリアル番号の対応に気をつければ、あとは大丈夫です。ただし、ピクセルが非常に小さいため、肉眼ではバラバラに見える色のブロックがつながって同じ色に染まる場合があることに注意してください。

写真を投稿するのを忘れたので、QQ で撮ったものをいくつか載せておきます。うっかり外枠を切り取ってしまいました。まあ、見てみましょう

以上が単色の QR コードをカラーの QR コードに変換する方法 (JavaScript)の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

声明:
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。