segunda-feira, 13 de novembro de 2017

Halftone Floyd-Steinberg - Falso e Verdadeiro

A implementação de Floyd-Steinberg conforme proposta no post anterior é na verdade considerada como False Floyd-Steinberg. Neste post, vamos propor uma melhoria, refatoração, daquele código, mas também uma implementação mais fidedigna do Floyd-Steinberg ao final. Como dito, o código do post anterior poderia ser melhorado, principalmente em sua parte inicial, na conversão para tons de cinza e na descoberta dos valores máximos e mínimos de branco e preto, que era feita em 2 etapas, pode ser feito em 1 etapa. Ainda, como a conversão para tons de cinza gera valores RGB iguais, não há necessidade de recálculo, basta calcular um deles.

Eis o código melhorado.
Function FloydSteinberg([System.Drawing.Image]$Img) {
    Write-Host -ForegroundColor Green "Aplicando efeito FloydSteinberg na imagem..."
    Write-Host -ForegroundColor Green "Será salvo na mesma pasta um arquivo NomeOriginal_FloydSteinberg.jpg"
    ## Primeiro converte a imagem para tons de cinza
    Write-Host "Convertendo para tons de cinza e calculando min/max de B/W"
    [Int32]$BLACK = 255
    [Int32]$WHITE = 0
    Foreach($y in (0..($Img.Height-1))) {
        Foreach($x in (0..($Img.Width-1))) {
            $ColorPixel = $Img.GetPixel($x,$y)
            $R = (0.2125*$ColorPixel.R)+(0.7154*$ColorPixel.G)+(0.0721*$ColorPixel.B)
            $G = $R
            $B = $R
            $A = $ColorPixel.A
            $Img.SetPixel($x,$y,[System.Drawing.Color]::FromArgb([Int32]$A,[Int32]$R,[Int32]$G,[Int32]$B))
            If ($R -lt $BLACK) { $BLACK = $R}
            If ($R -gt $WHITE) { $WHITE = $R}
        }
    }

    Write-Host "WHITE:$WHITE"
    Write-Host "BLACK:$BLACK"
    [Int32]$T = ($BLACK + $WHITE) / 2
    Write-Host "Aplicando Efeito FloydSteinberg..."
    [Int32]$p=0
    [Int32]$Err
    Foreach($y in (0..($Img.Height-1))) {
        $p++
        If ($p -gt (($Img.Height)*0.05)) { 
            Write-Host -NoNewLine '.'
            $p = 0
        }
        Foreach($x in (0..($Img.Width-1))) {
            $ColorPixel = $Img.GetPixel($x,$y)
            ## FloydSteinberg
            If ($ColorPixel.R -lt $T) {
                $R = $BLACK
                $G = $BLACK
                $B = $BLACK
                $Err = $ColorPixel.R - $BLACK
            }
            Else {
                $R = $WHITE
                $G = $WHITE
                $B = $WHITE
                $Err = $ColorPixel.R - $WHITE
            }
            ## SetPixel para efeito FloydSteinberg
            $A = $ColorPixel.A
            $Img.SetPixel($x,$y,[System.Drawing.Color]::FromArgb([Int32]$A,[Int32]$R,[Int32]$G,[Int32]$B))

            If (($x+1) -lt ($Img.Width-1)) {
                $CPx1 = $Img.GetPixel(($x+1),$y)
                $Ax1 = $CPx1.A
                $Rx1 = $CPx1.R + 3 * $Err / 8
                $Gx1 = $CPx1.G + 3 * $Err / 8
                $Bx1 = $CPx1.B + 3 * $Err / 8
                If ($Rx1 -lt 0) { $Rx1 = 0}
                If ($Gx1 -lt 0) { $Gx1 = 0}
                If ($Bx1 -lt 0) { $Bx1 = 0}
                If ($Rx1 -gt 255) { $Rx1 = 255}
                If ($Gx1 -gt 255) { $Gx1 = 255}
                If ($Bx1 -gt 255) { $Bx1 = 255}
                $Img.SetPixel(($x+1),$y,[System.Drawing.Color]::FromArgb([Int32]$Ax1,[Int32]$Rx1,[Int32]$Gx1,[Int32]$Bx1))
            }

            If (($y+1) -lt ($Img.Height-1)) {
                $CPy1 = $Img.GetPixel($x,($y+1))
                $Ay1 = $CPy1.A
                $Ry1 = $CPy1.R + 3 * $Err / 8
                $Gy1 = $CPy1.G + 3 * $Err / 8
                $By1 = $CPy1.B + 3 * $Err / 8
                If ($Ry1 -lt 0) { $Ry1 = 0}
                If ($Gy1 -lt 0) { $Gy1 = 0}
                If ($By1 -lt 0) { $By1 = 0}
                If ($Ry1 -gt 255) { $Ry1 = 255}
                If ($Gy1 -gt 255) { $Gy1 = 255}
                If ($By1 -gt 255) { $By1 = 255}
                $Img.SetPixel($x,($y+1),[System.Drawing.Color]::FromArgb([Int32]$Ay1,[Int32]$Ry1,[Int32]$Gy1,[Int32]$By1))
            }
            If ((($y+1) -lt ($Img.Height-1)) -And (($x+1) -lt ($Img.Width-1))) {
                $CPx1y1 = $Img.GetPixel(($x+1),($y+1))
                $Ax1y1 = $CPx1y1.A
                $Rx1y1 = $CPx1y1.R + $Err / 4
                $Gx1y1 = $CPx1y1.G + $Err / 4
                $Bx1y1 = $CPx1y1.B + $Err / 4
                If ($Rx1y1 -lt 0) { $Rx1y1 = 0}
                If ($Gx1y1 -lt 0) { $Gx1y1 = 0}
                If ($Bx1y1 -lt 0) { $Bx1y1 = 0}
                If ($Rx1y1 -gt 255) { $Rx1y1 = 255}
                If ($Gx1y1 -gt 255) { $Gx1y1 = 255}
                If ($Bx1y1 -gt 255) { $Bx1y1 = 255}
                $Img.SetPixel(($x+1),($y+1),[System.Drawing.Color]::FromArgb([Int32]$Ax1y1,[Int32]$Rx1y1,[Int32]$Gx1y1,[Int32]$Bx1y1))
            }
        } ## ForEach $x
    } ## ForEach $y
} ## EndFunction FloydSteinberg

$Arquivo = New-Object System.Windows.Forms.OpenFileDialog
$Arquivo.filter = "Imagens (*.PNG;*.BMP;*.JPG;*.JPEG;*.GIF)|*.PNG;*.BMP;*.JPG;*.JPEG;*.GIF"
$Arquivo.ShowDialog() | Out-Null
$ArquivoSemExt = ($Arquivo.filename).Substring(0,($Arquivo.filename).Length-4)
$Img = [System.Drawing.Image]::FromFile($Arquivo.filename)

Clear-Host
Write-Host "##############################################"
Write-Host "Arquivo de Imagem:"$Arquivo.filename
Write-Host "##############################################"

FloydSteinberg $Img

Write-Host ""
Write-Host -ForegroundColor Yellow "Conversão concluída!"
$Img.Save($ArquivoSemExt+"_FloydSteinberg.jpg","JPEG")
$Img.Dispose()


IMAGEM COLORIDA


EFEITO FALSE FLOYD-STEINBERG


Floyd-Steinberg Verdadeiro
Function FloydSteinberg([System.Drawing.Image]$Img) {
    Write-Host -ForegroundColor Green "Aplicando efeito FloydSteinberg na imagem..."
    Write-Host -ForegroundColor Green "Será salvo na mesma pasta um arquivo NomeOriginal_FloydSteinberg.jpg"
    ## Primeiro converte a imagem para tons de cinza
    Write-Host "Convertendo para tons de cinza e calculando min/max de B/W"
    [Int32]$BLACK = 255
    [Int32]$WHITE = 0
    Foreach($y in (0..($Img.Height-1))) {
        Foreach($x in (0..($Img.Width-1))) {
            $ColorPixel = $Img.GetPixel($x,$y)
            $R = (0.2125*$ColorPixel.R)+(0.7154*$ColorPixel.G)+(0.0721*$ColorPixel.B)
            $G = $R
            $B = $R
            $A = $ColorPixel.A
            $Img.SetPixel($x,$y,[System.Drawing.Color]::FromArgb([Int32]$A,[Int32]$R,[Int32]$G,[Int32]$B))
            If ($ColorPixel.R -lt $BLACK) { $BLACK = $ColorPixel.R}
            If ($ColorPixel.R -gt $WHITE) { $WHITE = $ColorPixel.R}
        }
    }
    Write-Host "WHITE:$WHITE"
    Write-Host "BLACK:$BLACK"
    [Int32]$T = ($BLACK + $WHITE) / 2
    Write-Host "Aplicando Efeito FloydSteinberg..."
    [Int32]$p=0
    [Int32]$quant_error=0
    Foreach($y in (0..($Img.Height-1))) {
        $p++
        If ($p -gt (($Img.Height)*0.05)) { 
            Write-Host -NoNewLine '.'
            $p = 0
        }
        Foreach($x in (0..($Img.Width-1))) {
            $ColorPixel = $Img.GetPixel($x,$y)
            ## FloydSteinberg
            If ($ColorPixel.R -lt $T) {
                $R = $BLACK
                $G = $BLACK
                $B = $BLACK
                $quant_error = $ColorPixel.R - $BLACK
            }
            Else {
                $R = $WHITE
                $G = $WHITE
                $B = $WHITE
                $quant_error = $ColorPixel.R - $WHITE
            }
            $A = $ColorPixel.A
            $Img.SetPixel($x,$y,[System.Drawing.Color]::FromArgb([Int32]$A,[Int32]$R,[Int32]$G,[Int32]$B))
            ## SetPixels FloydSteinberg (x+1,y)
            If (($x+1) -lt ($Img.Width-1)) {
                $CPx1 = $Img.GetPixel(($x+1),$y)
                $Ax1 = $CPx1.A
                $Rx1 = $CPx1.R + $quant_error * 7 / 16
                $Gx1 = $CPx1.G + $quant_error * 7 / 16
                $Bx1 = $CPx1.B + $quant_error * 7 / 16
                If ($Rx1 -lt 0) { $Rx1 = 0}
                If ($Gx1 -lt 0) { $Gx1 = 0}
                If ($Bx1 -lt 0) { $Bx1 = 0}
                If ($Rx1 -gt 255) { $Rx1 = 255}
                If ($Gx1 -gt 255) { $Gx1 = 255}
                If ($Bx1 -gt 255) { $Bx1 = 255}
                $Img.SetPixel(($x+1),$y,[System.Drawing.Color]::FromArgb([Int32]$Ax1,[Int32]$Rx1,[Int32]$Gx1,[Int32]$Bx1))
            }
            ## SetPixels FloydSteinberg (x-1,y+1)
            If ( (($y+1) -lt ($Img.Height-1)) -And (($x-1) -gt 0) ) {
                $CPx1y1 = $Img.GetPixel(($x-1),($y+1))
                $Ax1y1 = $CPx1y1.A
                $Rx1y1 = $CPx1y1.R + $quant_error * 3 / 16
                $Gx1y1 = $CPx1y1.G + $quant_error * 3 / 16
                $Bx1y1 = $CPx1y1.B + $quant_error * 3 / 16
                If ($Rx1y1 -lt 0) { $Rx1y1 = 0}
                If ($Gx1y1 -lt 0) { $Gx1y1 = 0}
                If ($Bx1y1 -lt 0) { $Bx1y1 = 0}
                If ($Rx1y1 -gt 255) { $Rx1y1 = 255}
                If ($Gx1y1 -gt 255) { $Gx1y1 = 255}
                If ($Bx1y1 -gt 255) { $Bx1y1 = 255}
                $Img.SetPixel(($x-1),($y+1),[System.Drawing.Color]::FromArgb([Int32]$Ax1y1,[Int32]$Rx1y1,[Int32]$Gx1y1,[Int32]$Bx1y1))
            }
            ## SetPixels FloydSteinberg (x,y+1)
            If (($y+1) -lt ($Img.Height-1)) {
                $CPy1 = $Img.GetPixel($x,($y+1))
                $Ay1 = $CPy1.A
                $Ry1 = $CPy1.R + $quant_error * 5 / 16
                $Gy1 = $CPy1.G + $quant_error * 5 / 16
                $By1 = $CPy1.B + $quant_error * 5 / 16
                If ($Ry1 -lt 0) { $Ry1 = 0}
                If ($Gy1 -lt 0) { $Gy1 = 0}
                If ($By1 -lt 0) { $By1 = 0}
                If ($Ry1 -gt 255) { $Ry1 = 255}
                If ($Gy1 -gt 255) { $Gy1 = 255}
                If ($By1 -gt 255) { $By1 = 255}
                $Img.SetPixel($x,($y+1),[System.Drawing.Color]::FromArgb([Int32]$Ay1,[Int32]$Ry1,[Int32]$Gy1,[Int32]$By1))
            }
            ## SetPixels FloydSteinberg (x+1,y+1)
            If ((($y+1) -lt ($Img.Height-1)) -And (($x+1) -lt ($Img.Width-1))) {
                $CPx1y1 = $Img.GetPixel(($x+1),($y+1))
                $Ax1y1 = $CPx1y1.A
                $Rx1y1 = $CPx1y1.R + $quant_error * 1 / 16
                $Gx1y1 = $CPx1y1.G + $quant_error * 1 / 16
                $Bx1y1 = $CPx1y1.B + $quant_error * 1 / 16
                If ($Rx1y1 -lt 0) { $Rx1y1 = 0}
                If ($Gx1y1 -lt 0) { $Gx1y1 = 0}
                If ($Bx1y1 -lt 0) { $Bx1y1 = 0}
                If ($Rx1y1 -gt 255) { $Rx1y1 = 255}
                If ($Gx1y1 -gt 255) { $Gx1y1 = 255}
                If ($Bx1y1 -gt 255) { $Bx1y1 = 255}
                $Img.SetPixel(($x+1),($y+1),[System.Drawing.Color]::FromArgb([Int32]$Ax1y1,[Int32]$Rx1y1,[Int32]$Gx1y1,[Int32]$Bx1y1))
            }
        } ## ForEach $x
    } ## ForEach $y
} ## EndFunction FloydSteinberg

$Arquivo = New-Object System.Windows.Forms.OpenFileDialog
$Arquivo.filter = "Imagens (*.PNG;*.BMP;*.JPG;*.JPEG;*.GIF)|*.PNG;*.BMP;*.JPG;*.JPEG;*.GIF"
$Arquivo.ShowDialog() | Out-Null
$ArquivoSemExt = ($Arquivo.filename).Substring(0,($Arquivo.filename).Length-4)
$Img = [System.Drawing.Image]::FromFile($Arquivo.filename)

Clear-Host
Write-Host "##############################################"
Write-Host "Arquivo de Imagem:"$Arquivo.filename
Write-Host "##############################################"

FloydSteinberg $Img

Write-Host ""
Write-Host -ForegroundColor Yellow "Conversão concluída!"
$Img.Save($ArquivoSemExt+"_FloydSteinberg.jpg","JPEG")
$Img.Dispose()

Abaixo comparativo entre o falso e o verdadeiro Floyd-Steinberg

EFEITO FALSE FLOYD-STEINBERG


EFEITO TRUE FLOYD-STEINBERG

Nenhum comentário:

Postar um comentário