ホームページ ウェブフロントエンド CSSチュートリアル Canvas を使用してピクセルを操作する方法

Canvas を使用してピクセルを操作する方法

Jun 14, 2018 pm 04:02 PM
canvas ピクセル

この記事は主に Canvas を使用してピクセルを操作する方法に関する関連情報を紹介します。内容は非常に優れているので、参考として共有します。

最新のブラウザは、<video> 要素によるビデオ再生をサポートしています。ほとんどのブラウザは、MediaDevices.getUserMedia() API を通じてカメラにアクセスすることもできます。しかし、これら 2 つを組み合わせたとしても、これらのピクセルに直接アクセスして操作することはできません。 <video>元素播放视频。大多数浏览器也可以通过 MediaDevices.getUserMedia()API访问摄像头。但即使这两件事结合起来,我们也无法直接访问和操纵这些像素。

幸运的是,浏览器有一个Canvas API,允许我们使用JavaScript绘制图形。实际上,我们可以从视频本身将图像绘制到 <canvas>

幸いなことに、ブラウザには、JavaScript を使用してグラフィックを描画できる Canvas API があります。実際にビデオ自体から <canvas> に画像を描画することで、それらのピクセルを操作して表示することができます。

ピクセルの操作方法についてここで学んだことは、キャンバスだけでなく、あらゆる種類やソースの画像やビデオを操作するための基礎を提供します。

キャンバスに画像を追加する

ビデオを始める前に、キャンバスに画像を追加する方法を見てみましょう。

<img src>
<p>
  <canvas id="Canvas" class="video"></canvas>
</p>
ログイン後にコピー

キャンバスに描画する画像を表す画像要素を作成します。あるいは、JavaScript で Image オブジェクトを使用することもできます。

var canvas;
var context;

function init() {
  var image = document.getElementById(&#39;SourceImage&#39;);
  canvas = document.getElementById(&#39;Canvas&#39;);
  context = canvas.getContext(&#39;2d&#39;);

  drawImage(image);
  // Or
  // var image = new Image();
  // image.onload = function () {
  //    drawImage(image);
  // }
  // image.src = &#39;image.jpg&#39;;
}

function drawImage(image) {
  // Set the canvas the same width and height of the image
  canvas.width = image.width;
  canvas.height = image.height;

  context.drawImage(image, 0, 0);
}

window.addEventListener(&#39;load&#39;, init);
ログイン後にコピー

上記のコードは、画像全体をキャンバスに描画します。

CodePen の Welling Guzman (@wellingguzman) 経由でキャンバス画像にペイントを表示します。

画像データを更新します

キャンバス上の画像データを使用して、ピクセルを操作したり変更したりできます。

data プロパティは、width、height、data の 3 つのプロパティを持つ ImageData オブジェクトであり、これらはすべて元の画像に基づいて表され、Uint8ClampedArray で表される 1 次元配列になります。 RGBA 形式の各ピクセルのデータを含むオブジェクト。

data プロパティは読み取り専用ですが、その値を変更できないというわけではありません。このプロパティには別の配列が割り当てられているということです。

// Get the canvas image data
var imageData = context.getImageData(0, 0, canvas.width, canvas.height);

image.data = new Uint8ClampedArray(); // WRONG
image.data[1] = 0; // CORRECT
ログイン後にコピー

Uint8ClampedArray オブジェクトはどのような値を表すのかと疑問に思われるかもしれません。これは MDN からの説明です:

Uint8ClampedArray 型の配列は、0 にクランプされた 8 ビットの符号なし整数の配列を表します。 255; [0,255] の範囲外の値を指定した場合は 0 または 255 が設定され、整数以外を指定した場合は最も近い整数が設定されます。確立されたオブジェクトは参照に使用できます。配列内の要素、または標準の配列インデックス構文を使用します (つまり括弧表記を使用します)

つまり、この配列は各位置に 0 から 255 の範囲の値を格納します。これにより、RGBA 形式になります。各部分は次のように表されるため、完璧なソリューションになります。 0 から 255 までの値。

RGBA カラー

色は、赤、緑、青の組み合わせである RGBA 形式で表現できます。

配列内の各位置は、色の不透明度を表します。カラー (ピクセル) チャネル値

  • 最初の位置は赤色の値

  • 2 番目の位置は緑色の値

  • 青色の値

  • 4 番目の位置はアルファ値

  • 5 番目の位置は次のピクセルの赤の値です

  • 6 番目の位置は次のピクセルの緑の値です

  • 7 番目の位置は次のピクセルの青の値です

  • 8 番目の位置は次のピクセルですアルファ値

  • など...

2x2 画像がある場合、16 ビット配列 (2x2 ピクセル x 4 つの値がそれぞれ) になります

2x2 画像は縮小されます

。配列は次のようになります:

// RED                 GREEN                BLUE                 WHITE
[ 255, 0, 0, 255,      0, 255, 0, 255,      0, 0, 255, 255,      255, 255, 255, 255]
ログイン後にコピー

ピクセルデータの変更

最も簡単な方法の1つは、すべてのRGBA値を255に変更して、すべてのピクセルを白に設定することです

// Use a button to trigger the "effect"
var button = document.getElementById(&#39;Button&#39;);

button.addEventListener(&#39;click&#39;, onClick);

function changeToWhite(data) {
  for (var i = 0; i < data.length; i++) {
    data[i] = 255;
  }
}

function onClick() {
  var imageData = context.getImageData(0, 0, canvas.width, canvas.height);

  changeToWhite(imageData.data);

  // Update the canvas with the new data
  context.putImageData(imageData, 0, 0);
}
ログイン後にコピー

データは参照として渡されます。つまり、データに変更を加えると、渡されたパラメータの値が変更されます。

色の反転

多くの計算を必要としない素晴らしい効果です。画像の色を反転します。

色の値は、XOR 演算子 (^) またはこの式 255 - 値 (値は 0 ~ 255 の間である必要があります) を使用して反転できます。

function invertColors(data) {
  for (var i = 0; i < data.length; i+= 4) {
    data[i] = data[i] ^ 255; // Invert Red
    data[i+1] = data[i+1] ^ 255; // Invert Green
    data[i+2] = data[i+2] ^ 255; // Invert Blue
  }
}

function onClick() {
  var imageData = context.getImageData(0, 0, canvas.width, canvas.height);

  invertColors(imageData.data);

  // Update the canvas with the new data
  context.putImageData(imageData, 0, 0);
}
ログイン後にコピー

以前のようにループを 1 ではなく 4 ずつインクリメントするので、配列内の 4 つの要素をピクセルからピクセルまで、各ピクセルに埋めることができます。

アルファ値は色の反転には影響しないため、スキップします。

明るさとコントラスト

次の式を使用して画像の明るさを調整します: newValue = currentValue + 255 * (明るさ / 100)。

明るさは-100から100の間でなければなりません
  1. currentValueは、赤、緑、または青の現在の照明値です。
  2. newValue は、現在のカラー ライトに明るさを加えた結果です
  3. 画像のコントラストの調整は、次の式で行うことができます:
  4. factor = (259 * (contrast + 255)) / (255 * (259 - contrast))
    color = GetPixelColor(x, y)
    newRed   = Truncate(factor * (Red(color)   - 128) + 128)
    newGreen = Truncate(factor * (Green(color) - 128) + 128)
    newBlue  = Truncate(factor * (Blue(color)  - 128) + 128)
    ログイン後にコピー

主な計算は、次のコントラストを取得することです。各カラー値要素に適用されます。切り捨ては、値が 0 ~ 255 の間にあることを保証する関数です。

これらの関数を JavaScript に記述してみましょう:

function applyBrightness(data, brightness) {
  for (var i = 0; i < data.length; i+= 4) {
    data[i] += 255 * (brightness / 100);
    data[i+1] += 255 * (brightness / 100);
    data[i+2] += 255 * (brightness / 100);
  }
}

function truncateColor(value) {
  if (value < 0) {
    value = 0;
  } else if (value > 255) {
    value = 255;
  }

  return value;
}

function applyContrast(data, contrast) {
  var factor = (259.0 * (contrast + 255.0)) / (255.0 * (259.0 - contrast));

  for (var i = 0; i < data.length; i+= 4) {
    data[i] = truncateColor(factor * (data[i] - 128.0) + 128.0);
    data[i+1] = truncateColor(factor * (data[i+1] - 128.0) + 128.0);
    data[i+2] = truncateColor(factor * (data[i+2] - 128.0) + 128.0);
  }
}
ログイン後にコピー

在这种情况下,您不需要truncateColor函数,因为Uint8ClampedArray会截断这些值,但为了翻译我们在其中添加的算法。

需要记住的一点是,如果应用亮度或对比度,则图像数据被覆盖后无法回到之前的状态。如果我们想要重置为原始状态,则原始图像数据必须单独存储以供参考。保持图像变量对其他函数可访问将会很有帮助,因为您可以使用该图像来重绘画布和原始图像。

var image = document.getElementById(&#39;SourceImage&#39;);

function redrawImage() {
  context.drawImage(image, 0, 0);
}
ログイン後にコピー

使用视频

为了使它适用于视频,我们将采用我们的初始图像脚本和HTML代码并做一些小的修改。

HTML

通过替换以下行来更改视频元素的Image元素:

 <img src>
ログイン後にコピー

...with this:

<video src></video>
ログイン後にコピー

JavaScript

替换这一行:

var image = document.getElementById(&#39;SourceImage&#39;);
ログイン後にコピー

...添加这行:

var video = document.getElementById(&#39;SourceVideo&#39;);
ログイン後にコピー

要开始处理视频,我们必须等到视频可以播放。

video.addEventListener(&#39;canplay&#39;, function () {
    // Set the canvas the same width and height of the video
    canvas.width = video.videoWidth;
    canvas.height = video.videoHeight;    

    // Play the video
    video.play();

    // start drawing the frames  
    drawFrame(video);
});
ログイン後にコピー

当有足够的数据可以播放媒体时,至少在几帧内播放事件播放。

我们无法看到画布上显示的任何视频,因为我们只显示第一帧。我们必须每n毫秒执行一次drawFrame以跟上视频帧速率。

在drawFrame内部,我们每10ms再次调用drawFrame。

function drawFrame(video) {
  context.drawImage(video, 0, 0);

  setTimeout(function () {
    drawFrame(video);
  }, 10);
}
ログイン後にコピー

在执行drawFrame之后,我们创建一个循环,每10ms执行一次drawFrame - 足够的时间让视频在画布中保持同步。

将效果添加到视频

我们可以使用我们之前创建的相同函数来反转颜色:

function invertColors(data) {
  for (var i = 0; i < data.length; i+= 4) {
    data[i] = data[i] ^ 255; // Invert Red
    data[i+1] = data[i+1] ^ 255; // Invert Green
    data[i+2] = data[i+2] ^ 255; // Invert Blue
  }
}
ログイン後にコピー

并将其添加到drawFrame函数中:

function drawFrame(video) {
  context.drawImage(video, 0, 0);

  var imageData = context.getImageData(0, 0, canvas.width, canvas.height);
  invertColors(imageData.data);
  context.putImageData(imageData, 0, 0);

  setTimeout(function () {
    drawFrame(video);
  }, 10);
}
ログイン後にコピー

我们可以添加一个按钮并切换效果:

function drawFrame(video) {
  context.drawImage(video, 0, 0);

  if (applyEffect) {
    var imageData = context.getImageData(0, 0, canvas.width, canvas.height);
    invertColors(imageData.data);
    context.putImageData(imageData, 0, 0);
  }

  setTimeout(function () {
    drawFrame(video);
  }, 10);
}
ログイン後にコピー

使用 camera

我们将保留我们用于视频的相同代码,唯一不同的是我们将使用MediaDevices.getUserMedia将视频流从文件更改为相机流。

MediaDevices.getUserMedia是弃用先前API MediaDevices.getUserMedia()的新API。浏览器仍旧支持旧版本,并且某些浏览器不支持新版本,我们必须求助于polyfill以确保浏览器支持其中一种。

首先,从视频元素中删除src属性:

<video><code>
<code>// Set the source of the video to the camera stream
function initCamera(stream) {
    video.src = window.URL.createObjectURL(stream);
}

if (navigator.mediaDevices.getUserMedia) {
  navigator.mediaDevices.getUserMedia({video: true, audio: false})
    .then(initCamera)
    .catch(console.error)
  );
}
ログイン後にコピー

Live Demo

效果

到目前为止,我们所介绍的所有内容都是我们需要的基础,以便为视频或图像创建不同的效果。我们可以通过独立转换每种颜色来使用很多不同的效果。

灰阶

将颜色转换为灰度可以使用不同的公式/技巧以不同的方式完成,以避免陷入太深的问题我将向您展示基于GIMP desaturate tool去饱和工具和Luma的五个公式:

Gray = 0.21R + 0.72G + 0.07B // Luminosity
Gray = (R + G + B) ÷ 3 // Average Brightness
Gray = 0.299R + 0.587G + 0.114B // rec601 standard
Gray = 0.2126R + 0.7152G + 0.0722B // ITU-R BT.709 standard
Gray = 0.2627R + 0.6780G + 0.0593B // ITU-R BT.2100 standard
ログイン後にコピー

我们想要使用这些公式找到的是每个像素颜色的亮度等级。该值的范围从0(黑色)到255(白色)。这些值将创建灰度(黑白)效果。

这意味着最亮的颜色将最接近255,最暗的颜色最接近0。

Live Demo

双色调

双色调效果和灰度效果的区别在于使用了两种颜色。在灰度上,您有一个从黑色到白色的渐变色,而在双色调中,您可以从任何颜色到任何其他颜色(从蓝色到粉红色)都有一个渐变。

使用灰度的强度值,我们可以将其替换为梯度值。

我们需要创建一个从ColorA到ColorB的渐变。

function createGradient(colorA, colorB) {   
  // Values of the gradient from colorA to colorB
  var gradient = [];
  // the maximum color value is 255
  var maxValue = 255;
  // Convert the hex color values to RGB object
  var from = getRGBColor(colorA);
  var to = getRGBColor(colorB);

  // Creates 256 colors from Color A to Color B
  for (var i = 0; i <= maxValue; i++) {
    // IntensityB will go from 0 to 255
    // IntensityA will go from 255 to 0
    // IntensityA will decrease intensity while instensityB will increase
    // What this means is that ColorA will start solid and slowly transform into ColorB
    // If you look at it in other way the transparency of color A will increase and the transparency of color B will decrease
    var intensityB = i;
    var intensityA = maxValue - intensityB;

    // The formula below combines the two color based on their intensity
    // (IntensityA * ColorA + IntensityB * ColorB) / maxValue
    gradient[i] = {
      r: (intensityA*from.r + intensityB*to.r) / maxValue,
      g: (intensityA*from.g + intensityB*to.g) / maxValue,
      b: (intensityA*from.b + intensityB*to.b) / maxValue
    };
  }

  return gradient;
}

// Helper function to convert 6digit hex values to a RGB color object
function getRGBColor(hex)
{
  var colorValue;

  if (hex[0] === &#39;#&#39;) {
    hex = hex.substr(1);
  }

  colorValue = parseInt(hex, 16);

  return {
    r: colorValue >> 16,
    g: (colorValue >> 8) & 255,
    b: colorValue & 255
  }
}
ログイン後にコピー

简而言之,我们从颜色A创建一组颜色值,降低强度,同时转到颜色B并增加强度。

从 #0096ff 到 #ff00f0

var gradients = [
  {r: 32, g: 144, b: 254},
  {r: 41, g: 125, b: 253},
  {r: 65, g: 112, b: 251},
  {r: 91, g: 96, b: 250},
  {r: 118, g: 81, b: 248},
  {r: 145, g: 65, b: 246},
  {r: 172, g: 49, b: 245},
  {r: 197, g: 34, b: 244},
  {r: 220, g: 21, b: 242},
  {r: 241, g: 22, b: 242},
];
ログイン後にコピー

缩放颜色过渡的表示

上面有一个从#0096ff到#ff00f0的10个颜色值的渐变示例。

颜色过渡的灰度表示

现在我们已经有了图像的灰度表示,我们可以使用它将其映射到双色调渐变值。

The duotone gradient has 256 colors while the grayscale has also 256 colors ranging from black (0) to white (255). That means a grayscale color value will map to a gradient element index.

var gradientColors = createGradient(&#39;#0096ff&#39;, &#39;#ff00f0&#39;);
var imageData = context.getImageData(0, 0, canvas.width, canvas.height);
applyGradient(imageData.data);

for (var i = 0; i < data.length; i += 4) {
  // Get the each channel color value
  var redValue = data[i];
  var greenValue = data[i+1];
  var blueValue = data[i+2];

  // Mapping the color values to the gradient index
  // Replacing the grayscale color value with a color for the duotone gradient
  data[i] = gradientColors[redValue].r;
  data[i+1] = gradientColors[greenValue].g;
  data[i+2] = gradientColors[blueValue].b;
  data[i+3] = 255;
}
ログイン後にコピー

Live Demo

结论

这个主题可以更深入或解释更多的影响。为你做的功课是找到可以应用于这些骨架示例的不同算法。

了解像素在画布上的结构将允许您创建无限数量的效果,如棕褐色,混色,绿色屏幕效果,图像闪烁/毛刺等。

您甚至可以在不使用图像或视频的情况下即时创建效果

以上がこの記事の全内容です。その他の関連コンテンツについては、PHP 中国語 Web サイトをご覧ください。

関連する推奨事項:

キャンバスの線のプロパティについて

キャンバスを使用して画像モザイクを実現する方法

以上がCanvas を使用してピクセルを操作する方法の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

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

ホットAIツール

Undresser.AI Undress

Undresser.AI Undress

リアルなヌード写真を作成する AI 搭載アプリ

AI Clothes Remover

AI Clothes Remover

写真から衣服を削除するオンライン AI ツール。

Undress AI Tool

Undress AI Tool

脱衣画像を無料で

Clothoff.io

Clothoff.io

AI衣類リムーバー

AI Hentai Generator

AI Hentai Generator

AIヘンタイを無料で生成します。

ホットツール

メモ帳++7.3.1

メモ帳++7.3.1

使いやすく無料のコードエディター

SublimeText3 中国語版

SublimeText3 中国語版

中国語版、とても使いやすい

ゼンドスタジオ 13.0.1

ゼンドスタジオ 13.0.1

強力な PHP 統合開発環境

ドリームウィーバー CS6

ドリームウィーバー CS6

ビジュアル Web 開発ツール

SublimeText3 Mac版

SublimeText3 Mac版

神レベルのコード編集ソフト(SublimeText3)

GIMPでピクセルアートを作成する方法 GIMPでピクセルアートを作成する方法 Feb 19, 2024 pm 03:24 PM

この記事は、Windows でのピクセル アート作成に GIMP を使用することに興味がある場合に役立ちます。 GIMP は、無料でオープンソースであるだけでなく、美しい画像やデザインを簡単に作成できる有名なグラフィック編集ソフトウェアです。 GIMP は、初心者にもプロのデザイナーにも同様に適していることに加えて、描画と作成のための唯一の構成要素としてピクセルを利用するデジタル アートの形式であるピクセル アートの作成にも使用できます。 GIMP でピクセル アートを作成する方法 Windows PC で GIMP を使用してピクセル アートを作成する主な手順は次のとおりです。 GIMP をダウンロードしてインストールし、アプリケーションを起動します。新しいイメージを作成します。幅と高さのサイズを変更します。鉛筆ツールを選択します。ブラシの種類をピクセルに設定します。設定

Meitu Xiuxiu でピクセルを変更する方法 Meitu Xiuxiu でピクセルを変更する方法 Meitu Xiuxiu でピクセルを変更する方法 Meitu Xiuxiu でピクセルを変更する方法 Mar 12, 2024 pm 02:50 PM

Meitu Xiuxiuのピクセルを変更するにはどうすればよいですか? Meitu Xiuxiu は、ユーザーに優れた写真編集体験を提供することに特化した、多くの機能を備えたモバイル写真編集ソフトウェアです。ソフトウェアでは、ポートレートの美しさ、肌の美白、顔の整形、小顔など、写真に対して多くの操作を実行できます。満足できない場合は、クリックするだけで完璧なプロポーションを簡単に作成できます。修復した写真については、保存する前にサイズとピクセルを調整することもできます。それで、ピクセル化する方法を知っていますか?まだ知らない人のために、以下の編集者が共有した方法を見てみましょう。 MeituXiuXiu のピクセルを変更する方法 1. ダブルクリックして MeituXiuXiu を開き、「画像の美化」オプションをクリックして選択します; 2. 画像の美化で、「サイズ」をクリックします。

Meitu Xiuxiuのピクセル高さを設定する方法 Meitu Xiuxiuのピクセル高さを設定する方法 Mar 27, 2024 am 11:00 AM

デジタル時代において、写真は私たちの日常生活や仕事に不可欠な部分になっています。ソーシャルメディアで共有する場合でも、仕事のレポートで発表する場合でも、高品質の写真は多くのポイントを追加します。しかし、多くの場合、手持ちの写真のピクセルが満足のいくものではないため、さまざまなシーンのニーズに合わせてピクセルの高さを調整するツールを使用する必要があります。そこで、このチュートリアルガイドでは、Meitu Xiuxiu を使って写真のピクセルを調整する方法を詳しく紹介します。まず、携帯電話で[Meitu Xiu Xiu]アイコンを見つけ、クリックしてメインインターフェイスに入り、[Beauty Pictures]項目をクリックしてください。 2. 次に、第 2 ステップとして、図に示すように [カメラロール] ページが表示されます。自分をクリックしてください。

キャンバス矢印プラグインとは何ですか? キャンバス矢印プラグインとは何ですか? Aug 21, 2023 pm 02:14 PM

キャンバス矢印プラグインには、1. シンプルで使いやすい API を備え、カスタムの矢印効果を作成できる Fabric.js、2. 矢印を描画する機能を提供し、さまざまな矢印を作成できる Konva.js が含まれます。スタイル; 3. 豊富なグラフィックス処理機能を提供し、さまざまな矢印効果を実現できる Pixi.js; 4. 矢印のスタイルやアニメーションを簡単に作成および制御できる Two.js; 5. さまざまな矢印効果を作成できる Arrow.js ; 6. 大まかな.jsでは、手描きの矢印などが作成できます。

html2canvas にはどのようなバージョンがありますか? html2canvas にはどのようなバージョンがありますか? Aug 22, 2023 pm 05:58 PM

html2canvas のバージョンには、html2canvas v0.x、html2canvas v1.x などが含まれます。詳細な紹介: 1. html2canvas v0.x (html2canvas の初期バージョン) 最新の安定バージョンは v0.5.0-alpha1 です。これは、多くのプロジェクトで広く使用され、検証されている成熟したバージョンです。2. html2canvas v1.x、これは html2canvas の新しいバージョンです。

キャンバスクロックの詳細は何ですか? キャンバスクロックの詳細は何ですか? Aug 21, 2023 pm 05:07 PM

キャンバス時計の詳細には、時計の外観、目盛り、デジタル時計、時針、分針、秒針、中心点、アニメーション効果、その他のスタイルなどが含まれます。詳細な紹介: 1. 時計の外観、キャンバスを使用して時計の外観として円形の文字盤を描画し、文字盤のサイズ、色、境界線などのスタイルを設定できます; 2. 目盛り線、目盛り線を描画します。位置; 3. デジタル時計、現在の時と分を示すために文字盤にデジタル時計を描くことができます; 4. 時針、分針、秒針など。

tkinter Canvas にはどのようなプロパティがありますか? tkinter Canvas にはどのようなプロパティがありますか? Aug 21, 2023 pm 05:46 PM

tkinter Canvas 属性には、bg、bd、relief、width、height、cursor、highlightbackground、highlightcolor、highlightthickness、insertbackground、insertwidth、selectbackground、selectforeground、xscrollcommand 属性などが含まれます。詳しい紹介

uniapp は、キャンバスを使用してチャートやアニメーション効果を描画する方法を実装します。 uniapp は、キャンバスを使用してチャートやアニメーション効果を描画する方法を実装します。 Oct 18, 2023 am 10:42 AM

キャンバスを使用して uniapp でチャートやアニメーション効果を描画する方法には、特定のコード例が必要です。 1. はじめに モバイル デバイスの普及に伴い、モバイル端末上でさまざまなチャートやアニメーション効果を表示する必要があるアプリケーションがますます増えています。 uniapp は、Vue.js に基づくクロスプラットフォーム開発フレームワークとして、キャンバスを使用してチャートやアニメーション効果を描画する機能を提供します。この記事では、uniapp がキャンバスを使用してチャートやアニメーション効果を実現する方法を紹介し、具体的なコード例を示します。 2.キャンバス

See all articles