最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

使用JavaScript實(shí)現(xiàn)將普通圖片實(shí)時(shí)轉(zhuǎn)換為點(diǎn)陣圖的效果

 更新時(shí)間:2025年11月21日 09:10:06   作者:軍軍360  
在數(shù)字圖像的世界里,我們有時(shí)會(huì)癡迷于一種復(fù)古的美學(xué)——點(diǎn)陣圖,從早期的打印機(jī)輸出到LED廣告牌,那種由無數(shù)小圓點(diǎn)構(gòu)成的圖像,散發(fā)著獨(dú)特的科技感和藝術(shù)氣息,所以本文我們將一起探索如何使用JavaScript實(shí)現(xiàn)將普通圖片實(shí)時(shí)轉(zhuǎn)換為點(diǎn)陣圖的效果

引言

在數(shù)字圖像的世界里,我們有時(shí)會(huì)癡迷于一種復(fù)古的美學(xué)——點(diǎn)陣圖。從早期的打印機(jī)輸出到LED廣告牌,那種由無數(shù)小圓點(diǎn)構(gòu)成的圖像,散發(fā)著獨(dú)特的科技感和藝術(shù)氣息。今天,我們將一起探索如何使用現(xiàn)代Web技術(shù)(HTML5 Canvas和JavaScript),在瀏覽器中實(shí)現(xiàn)將普通圖片實(shí)時(shí)轉(zhuǎn)換為點(diǎn)陣圖的效果。

一、效果展示與核心思路

最終效果:在網(wǎng)頁(yè)上上傳一張圖片,它將立刻被轉(zhuǎn)換成一個(gè)由許多小圓點(diǎn)組成的、具有黑白版畫風(fēng)格的圖像。你可以通過調(diào)整參數(shù)來控制點(diǎn)陣的疏密和大小。

核心思路

  1. 繪制原圖:將用戶上傳的圖片繪制到一個(gè)隱藏的Canvas上。
  2. 網(wǎng)格采樣:將這個(gè)Canvas劃分成均勻的網(wǎng)格。每個(gè)網(wǎng)格單元最終會(huì)對(duì)應(yīng)點(diǎn)陣圖中的一個(gè)“點(diǎn)”(或留白)。
  3. 計(jì)算灰度:對(duì)于每個(gè)網(wǎng)格單元,我們計(jì)算其內(nèi)部所有像素的平均亮度(或稱灰度值)。
  4. 閾值判定:根據(jù)一個(gè)預(yù)設(shè)的閾值,決定當(dāng)前網(wǎng)格是“畫點(diǎn)”還是“不畫點(diǎn)”。如果該區(qū)域的平均亮度低于閾值(表示較暗),我們就畫一個(gè)實(shí)心圓點(diǎn);如果亮度較高,則留白。
  5. 繪制點(diǎn)陣:在另一個(gè)Canvas上,根據(jù)步驟4的判定結(jié)果,在對(duì)應(yīng)的網(wǎng)格位置繪制圓點(diǎn)。

二、代碼實(shí)現(xiàn)(附詳細(xì)注釋)

讓我們直接看代碼,這是理解整個(gè)過程最直觀的方式。

HTML結(jié)構(gòu)

創(chuàng)建一個(gè)簡(jiǎn)單的上傳界面和兩個(gè)Canvas:一個(gè)用于幕后處理原圖,一個(gè)用于展示最終的點(diǎn)陣藝術(shù)。

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>圖片點(diǎn)陣化工具</title>
    <style>
        body {
            font-family: sans-serif;
            display: flex;
            flex-direction: column;
            align-items: center;
            padding: 20px;
        }
        .controls {
            margin-bottom: 20px;
        }
        #originalCanvas {
            display: none; /* 隱藏處理用的Canvas */
        }
        #dotMatrixCanvas {
            border: 1px solid #ccc;
            margin-top: 20px;
        }
    </style>
</head>
<body>
    <h1>圖片點(diǎn)陣化效果</h1>
    
    <div class="controls">
        <input type="file" id="imageUpload" accept="image/*">
        

        <label>點(diǎn)大小: <input type="range" id="dotSizeSlider" min="2" max="20" value="6"></label>
        <span id="dotSizeValue">6</span>
        

        <label>點(diǎn)間距: <input type="range" id="spacingSlider" min="5" max="50" value="15"></label>
        <span id="spacingValue">15</span>
        

        <label>閾值 (值越小點(diǎn)越少): <input type="range" id="thresholdSlider" min="0" max="255" value="128"></label>
        <span id="thresholdValue">128</span>
    </div>

    <!-- 用于處理原始圖像的Canvas,不顯示 -->
    <canvas id="originalCanvas"></canvas>
    
    <!-- 用于顯示點(diǎn)陣效果的Canvas -->
    <canvas id="dotMatrixCanvas"></canvas>

    <script src="script.js"></script>
</body>
</html>

JavaScript核心邏輯 (script.js)

這是實(shí)現(xiàn)點(diǎn)陣化效果的核心代碼。

// 獲取DOM元素
const fileInput = document.getElementById('imageUpload');
const dotSizeSlider = document.getElementById('dotSizeSlider');
const spacingSlider = document.getElementById('spacingSlider');
const thresholdSlider = document.getElementById('thresholdSlider');
const dotSizeValue = document.getElementById('dotSizeValue');
const spacingValue = document.getElementById('spacingValue');
const thresholdValue = document.getElementById('thresholdValue');
const originalCanvas = document.getElementById('originalCanvas');
const dotMatrixCanvas = document.getElementById('dotMatrixCanvas');

const ctxOriginal = originalCanvas.getContext('2d');
const ctxDotMatrix = dotMatrixCanvas.getContext('2d');

// 初始化變量
let dotSize = parseInt(dotSizeSlider.value);
let spacing = parseInt(spacingSlider.value);
let threshold = parseInt(thresholdSlider.value);

// 更新顯示值的函數(shù)
function updateSliderValues() {
    dotSizeValue.textContent = dotSize;
    spacingValue.textContent = spacing;
    thresholdValue.textContent = threshold;
}

// 監(jiān)聽滑塊變化
dotSizeSlider.addEventListener('input', (e) => {
    dotSize = parseInt(e.target.value);
    updateSliderValues();
    if (currentImage) convertToDotMatrix(currentImage);
});

spacingSlider.addEventListener('input', (e) => {
    spacing = parseInt(e.target.value);
    updateSliderValues();
    if (currentImage) convertToDotMatrix(currentImage);
});

thresholdSlider.addEventListener('input', (e) => {
    threshold = parseInt(e.target.value);
    updateSliderValues();
    if (currentImage) convertToDotMatrix(currentImage);
});

let currentImage = null;

// 監(jiān)聽文件上傳
fileInput.addEventListener('change', function(e) {
    const file = e.target.files[0];
    if (!file || !file.type.match('image.*')) return;

    const reader = new FileReader();
    
    reader.onload = function(event) {
        const img = new Image();
        img.onload = function() {
            currentImage = img;
            convertToDotMatrix(img);
        };
        img.src = event.target.result;
    };
    reader.readAsDataURL(file);
});

// 核心函數(shù):將圖片轉(zhuǎn)換為點(diǎn)陣
function convertToDotMatrix(image) {
    // 1. 設(shè)置原始Canvas尺寸為圖片尺寸,并繪制圖片
    originalCanvas.width = image.width;
    originalCanvas.height = image.height;
    ctxOriginal.clearRect(0, 0, originalCanvas.width, originalCanvas.height);
    ctxOriginal.drawImage(image, 0, 0);

    // 2. 計(jì)算點(diǎn)陣Canvas的尺寸
    // 點(diǎn)陣圖的寬高由網(wǎng)格數(shù)量(原圖尺寸/間距)和點(diǎn)的大小決定
    const cols = Math.ceil(originalCanvas.width / spacing);
    const rows = Math.ceil(originalCanvas.height / spacing);
    
    dotMatrixCanvas.width = cols * spacing;
    dotMatrixCanvas.height = rows * spacing;

    // 3. 清除點(diǎn)陣Canvas,設(shè)置白色背景
    ctxDotMatrix.fillStyle = 'white';
    ctxDotMatrix.fillRect(0, 0, dotMatrixCanvas.width, dotMatrixCanvas.height);
    ctxDotMatrix.fillStyle = 'black'; // 設(shè)置點(diǎn)的顏色為黑色

    // 4. 獲取原始Canvas的像素?cái)?shù)據(jù)
    // ImageData.data 是一個(gè)一維數(shù)組,包含 [R, G, B, A, R, G, B, A, ...] 格式的數(shù)據(jù)
    const imageData = ctxOriginal.getImageData(0, 0, originalCanvas.width, originalCanvas.height);
    const data = imageData.data;

    // 5. 遍歷網(wǎng)格,進(jìn)行采樣和繪制
    for (let y = 0; y < rows; y++) {
        for (let x = 0; x < cols; x++) {
            // 計(jì)算當(dāng)前網(wǎng)格在原始圖像上的起始像素位置
            const startX = x * spacing;
            const startY = y * spacing;

            // 6. 計(jì)算當(dāng)前網(wǎng)格區(qū)域的平均亮度
            let totalBrightness = 0;
            let sampleCount = 0;

            // 在網(wǎng)格內(nèi)采樣像素(可以優(yōu)化為間隔采樣以提高性能)
            for (let subY = startY; subY < startY + spacing && subY < originalCanvas.height; subY++) {
                for (let subX = startX; subX < startX + spacing && subX < originalCanvas.width; subX++) {
                    // 計(jì)算當(dāng)前像素在ImageData數(shù)組中的索引
                    const pixelIndex = (subY * originalCanvas.width + subX) * 4;
                    const r = data[pixelIndex];     // 紅色值 (0-255)
                    const g = data[pixelIndex + 1]; // 綠色值 (0-255)
                    const b = data[pixelIndex + 2]; // 藍(lán)色值 (0-255)

                    // !?。『诵闹R(shí)點(diǎn):計(jì)算像素的亮度(灰度值)
                    // 使用標(biāo)準(zhǔn)亮度公式,模擬人眼對(duì)不同顏色的敏感度
                    // 權(quán)重:綠色最敏感(0.587) > 紅色次之(0.299) > 藍(lán)色最不敏感(0.114)
                    const brightness = 0.299 * r + 0.587 * g + 0.114 * b;
                    
                    totalBrightness += brightness;
                    sampleCount++;
                }
            }

            // 計(jì)算網(wǎng)格區(qū)域的平均亮度
            const averageBrightness = totalBrightness / sampleCount;

            // 7. 閾值判定:如果平均亮度低于閾值,則繪制圓點(diǎn)
            if (averageBrightness < threshold) {
                // 計(jì)算圓點(diǎn)在點(diǎn)陣Canvas上的中心坐標(biāo)
                const posX = x * spacing + spacing / 2;
                const posY = y * spacing + spacing / 2;

                // 繪制實(shí)心圓
                ctxDotMatrix.beginPath();
                ctxDotMatrix.arc(posX, posY, dotSize / 2, 0, Math.PI * 2);
                ctxDotMatrix.fill();
            }
            // 否則(亮度高于閾值),留白,不進(jìn)行繪制
        }
    }
}

// 初始化滑塊顯示值
updateSliderValues();

三、核心原理解析:亮度公式

代碼中最關(guān)鍵的一行是:

const brightness = 0.299 * r + 0.587 * g + 0.114 * b;

這行代碼使用的是灰度轉(zhuǎn)換的標(biāo)準(zhǔn)算法(ITU-R BT.601) 。為什么不能簡(jiǎn)單地將RGB值平均 (r + g + b) / 3呢?

因?yàn)槿搜垡暰W(wǎng)膜上的三種感光細(xì)胞對(duì)不同顏色的敏感度是不同的:對(duì)綠色最敏感,紅色次之,藍(lán)色最不敏感。這個(gè)加權(quán)平均公式(綠色權(quán)重0.587最高,藍(lán)色0.114最低)能夠計(jì)算出更符合人眼主觀亮度感知的灰度值,使得轉(zhuǎn)換后的點(diǎn)陣圖明暗關(guān)系更加自然和準(zhǔn)確。

四、參數(shù)調(diào)整與效果優(yōu)化

通過操作界面上的三個(gè)滑塊,你可以創(chuàng)造出風(fēng)格迥異的點(diǎn)陣效果:

點(diǎn)大小 (dotSize) :控制每個(gè)圓點(diǎn)的半徑。點(diǎn)越大,圖像越粗獷,細(xì)節(jié)越少;點(diǎn)越小,則越精細(xì)。

點(diǎn)間距 (spacing) :控制網(wǎng)格的密度。間距越大,點(diǎn)陣越稀疏,圖像越抽象;間距越小,點(diǎn)陣越密集,保留的細(xì)節(jié)越多。

閾值 (threshold) :這是控制圖像對(duì)比度的關(guān)鍵參數(shù)。

  • 調(diào)低閾值 (如 50) :只有非常暗的區(qū)域才會(huì)畫點(diǎn),生成的圖像點(diǎn)很少,整體很“淡”。
  • 調(diào)高閾值 (如 200) :大量灰色區(qū)域也會(huì)被判定為需要畫點(diǎn),生成的圖像點(diǎn)很密集,整體很“濃”,對(duì)比度降低。

小技巧:嘗試使用高間距 + 大點(diǎn)尺寸來創(chuàng)造抽象的藝術(shù)海報(bào)效果,或者使用低間距 + 小點(diǎn)尺寸來制作精細(xì)的肖像郵票效果。

五、總結(jié)

通過這個(gè)項(xiàng)目,僅實(shí)現(xiàn)了一個(gè)有趣的圖像處理工具,還深入理解了像素操作、灰度轉(zhuǎn)換和采樣等基本圖形學(xué)概念。這個(gè)基礎(chǔ)版本還有巨大的拓展空間:

  1. 彩色點(diǎn)陣:可以為暗、中、亮部區(qū)域分配不同的顏色,而不是只用黑色。
  2. 異形點(diǎn):將圓點(diǎn)替換為方形、三角形甚至自定義形狀。
  3. 動(dòng)態(tài)化:將點(diǎn)陣化效果應(yīng)用于視頻流,實(shí)現(xiàn)實(shí)時(shí)點(diǎn)陣攝像頭。
  4. 性能優(yōu)化:對(duì)于大圖,可以采用間隔采樣等策略提升處理速度。

以上就是使用JavaScript實(shí)現(xiàn)將普通圖片實(shí)時(shí)轉(zhuǎn)換為點(diǎn)陣圖的效果的詳細(xì)內(nèi)容,更多關(guān)于JavaScript圖片轉(zhuǎn)為點(diǎn)陣圖的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

最新評(píng)論

瑞金市| 综艺| 资源县| 武夷山市| 南澳县| 泸定县| 定西市| 济阳县| 治县。| 松潘县| 钦州市| 锦州市| 山丹县| 平顶山市| 横峰县| 尼勒克县| 怀仁县| 双流县| 万安县| 彩票| 江门市| 调兵山市| 鲁甸县| 昌宁县| 铁岭市| 南溪县| 高阳县| 永州市| 紫阳县| 澄迈县| 福州市| 万州区| 太湖县| 库尔勒市| 全南县| 华宁县| 克东县| 剑阁县| 辛集市| 神农架林区| 盐津县|