使用您自己的算法将灰度图像抖动为纯黑白图像。
准则:您必须提出自己的新算法。您不能使用预先存在的算法(例如Floyd-Steinburg),但可以使用常规技术。您的程序必须能够读取图像并产生相同大小的图像。这是一场人气竞赛,因此谁能产生最好的(最接近原始的)和最具创造力的(由投票决定)的胜利。如果代码简短,则可以加分,尽管这不是必需的。
您可以使用任何想要的灰度图像作为输入,它应该大于300x300。任何文件格式都可以。
输入示例:

输出示例:

这是一项很好的工作,但是仍然有可见的线条和图案。
使用您自己的算法将灰度图像抖动为纯黑白图像。
准则:您必须提出自己的新算法。您不能使用预先存在的算法(例如Floyd-Steinburg),但可以使用常规技术。您的程序必须能够读取图像并产生相同大小的图像。这是一场人气竞赛,因此谁能产生最好的(最接近原始的)和最具创造力的(由投票决定)的胜利。如果代码简短,则可以加分,尽管这不是必需的。
您可以使用任何想要的灰度图像作为输入,它应该大于300x300。任何文件格式都可以。
输入示例:

输出示例:

这是一项很好的工作,但是仍然有可见的线条和图案。
Answers:
好的,我使用的是称为FITS的模糊图像格式,用于天文学。这意味着有一个用于读取和写入此类图像的Fortran库。另外,ImageMagick和Gimp都可以读取/写入FITS图像。
我使用的算法基于“ Sierra Lite”抖动,但有两个改进:
a)我将传播的误差减少了4/5。
b)我在扩散矩阵中引入随机变化,同时保持其总和不变。
这些几乎一起完全消除了OP示例中的模式。
假设您已安装CFITSIO库,请使用
gfortran -lcfitsio抖动.f90
文件名是硬编码的(无法解决此问题)。
码:
program dither
integer :: status,unit,readwrite,blocksize,naxes(2),nfound
integer :: group,npixels,bitpix,naxis,i,j,fpixel,un
real :: nullval,diff_mat(3,2),perr
real, allocatable :: image(:,:), error(:,:)
integer, allocatable :: seed(:)
logical :: anynull,simple,extend
character(len=80) :: filename
call random_seed(size=Nrand)
allocate(seed(Nrand))
open(newunit=un,file="/dev/urandom",access="stream",&
form="unformatted",action="read",status="old")
read(un) seed
close(un)
call random_seed(put=seed)
deallocate(seed)
status=0
call ftgiou(unit,status)
filename='PUPPY.FITS'
readwrite=0
call ftopen(unit,filename,readwrite,blocksize,status)
call ftgknj(unit,'NAXIS',1,2,naxes,nfound,status)
call ftgidt(unit,bitpix,status)
npixels=naxes(1)*naxes(2)
group=1
nullval=-999
allocate(image(naxes(1),naxes(2)))
allocate(error(naxes(1)+1,naxes(2)+1))
call ftgpve(unit,group,1,npixels,nullval,image,anynull,status)
call ftclos(unit, status)
call ftfiou(unit, status)
diff_mat=0.0
diff_mat(3,1) = 2.0
diff_mat(1,2) = 1.0
diff_mat(2,2) = 1.0
diff_mat=diff_mat/5.0
error=0.0
perr=0
do j=1,naxes(2)
do i=1,naxes(1)
p=max(min(image(i,j)+error(i,j),255.0),0.0)
if (p < 127.0) then
perr=p
image(i,j)=0.0
else
perr=p-255.0
image(i,j)=255.0
endif
call random_number(r)
r=0.6*(r-0.5)
error(i+1,j)= error(i+1,j) +perr*(diff_mat(3,1)+r)
error(i-1,j+1)=error(i-1,j+1)+perr*diff_mat(1,2)
error(i ,j+1)=error(i ,j+1) +perr*(diff_mat(2,2)-r)
end do
end do
call ftgiou(unit,status)
blocksize=1
filename='PUPPY-OUT.FITS'
call ftinit(unit,filename,blocksize,status)
simple=.true.
naxis=2
extend=.true.
call ftphpr(unit,simple,bitpix,naxis,naxes,0,1,extend,status)
group=1
fpixel=1
call ftppre(unit,group,fpixel,npixels,image,status)
call ftclos(unit, status)
call ftfiou(unit, status)
deallocate(image)
deallocate(error)
end program dither
OP帖子中小狗图像的

示例输出:OP示例输出:
magick B2DBy.jpg -gamma .45455 -ordered-dither [all] 4x4 ordered4x4g45.pbm
在抱怨我使用“已建立的算法”之前,请阅读2003年4月的GraphicsMagick和ImageMagick的ChangeLog,您会发现我在这些应用程序中实现了该算法。同样,“-gamma .45455”与“有序抖动”的组合是新的。
“ -gamma .45455”可确保图像太亮。只有GraphicsMagick才需要“ all”参数。
之所以会有条带,是因为4x4有序抖动图像中只有17个灰度级。通过使用具有65个电平的8x8有序抖动,可以减少出现带状的现象。
我更喜欢有序抖动版本,但为了完整性起见,我还包括了随机阈值版本。
magick B2DBy.jpg -gamma .6 -random-threshold 10x90% random-threshold.pbm
“ 10x90%”表示将强度低于10%的像素渲染为纯黑色,将强度高于90%的像素渲染为纯白色,以避免在这些区域中出现一些孤独的斑点。
可能值得一提的是,两者都尽可能地提高了内存效率。也不进行任何扩散,因此即使编写有序抖动块,它们也一次工作一个像素,并且不需要了解任何有关相邻像素的信息。ImageMagick和GraphicsMagick一次处理一行,但是这些方法不是必需的。在我的旧x86_64计算机上,有序抖动转换的实时时间不到0.04秒。
我为代码样式表示歉意,我使用我们刚刚在java类中构建的一些库将其整合在一起,并且它存在复制粘贴和幻数的不良情况。该算法选择图像中的随机矩形,并检查抖动图像或原始图像中的平均亮度是否更大。然后,它打开或关闭像素以使亮度更接近一致,优先选择与原始图像差异更大的像素。我认为这样做可以更好地表现出像小狗的头发这样的细小细节,但是图像噪声更大,因为即使在没有毛发的地方,它也会尝试表现出细节。

public void dither(){
int count = 0;
ditheredFrame.suspendNotifications();
while(count < 1000000){
count ++;
int widthw = 5+r.nextInt(5);
int heightw = widthw;
int minx = r.nextInt(width-widthw);
int miny = r.nextInt(height-heightw);
Frame targetCropped = targetFrame.crop(minx, miny, widthw, heightw);
Frame ditherCropped = ditheredFrame.crop(minx, miny, widthw, heightw);
if(targetCropped.getAverage().getBrightness() > ditherCropped.getAverage().getBrightness() ){
int x = 0;
int y = 0;
double diff = 0;
for(int i = 1; i < ditherCropped.getWidth()-1; i ++){
for(int j = 1; j < ditherCropped.getHeight()-1; j ++){
double d = targetCropped.getPixel(i, j).getBrightness() - ditherCropped.getPixel(i, j).getBrightness();
d += .005* targetCropped.getPixel(i+1, j).getBrightness() - .005*ditherCropped.getPixel(i+1, j).getBrightness();
d += .005* targetCropped.getPixel(i-1, j).getBrightness() - .005*ditherCropped.getPixel(i-1, j).getBrightness();
d += .005* targetCropped.getPixel(i, j+1).getBrightness() -.005* ditherCropped.getPixel(i, j+1).getBrightness();
d += .005* targetCropped.getPixel(i, j-1).getBrightness() - .005*ditherCropped.getPixel(i, j-1).getBrightness();
if(d > diff){
diff = d;
x = i;
y = j;
}
}
ditherCropped.setPixel(x, y, WHITE);
}
} else {
int x = 0;
int y = 0;
double diff = 0;
for(int i = 1; i < ditherCropped.getWidth()-1; i ++){
for(int j = 1; j < ditherCropped.getHeight()-1; j ++){
double d = ditherCropped.getPixel(i, j).getBrightness() -targetCropped.getPixel(i, j).getBrightness();
d += -.005* targetCropped.getPixel(i+1, j).getBrightness() +.005* ditherCropped.getPixel(i+1, j).getBrightness();
d += -.005* targetCropped.getPixel(i-1, j).getBrightness() +.005* ditherCropped.getPixel(i+1, j).getBrightness();
d += -.005* targetCropped.getPixel(i, j+1).getBrightness() + .005*ditherCropped.getPixel(i, j+1).getBrightness();
d += -.005* targetCropped.getPixel(i, j-1).getBrightness() + .005*ditherCropped.getPixel(i, j-1).getBrightness();
if(d > diff){
diff = d;
x = i;
y = j;
}
}
ditherCropped.setPixel(x, y, BLACK);
}
}
}
ditheredFrame.resumeNotifications();
}
远非我自己的“新算法”,但抱歉,无法抗拒它。
convert puppy.jpg puppy.pdf && \
convert puppy.jpg -depth 8 -colorspace Gray -resize 20x20! -negate gray:- | \
gs -q -sDEVICE=ps2write -o- -c \
'<</Thresholds (%stdin) (r) file 400 string readstring pop
/HalftoneType 3 /Width 20 /Height 20
>>sethalftone' \
-f puppy.pdf | \
gs -q -sDEVICE=pngmono -o puppy.png -

当然,它在没有“相同大小”约束的情况下效果更好。
这是我的意见书。拍摄JPG图像,计算每个像素的像素亮度(由于这个 SO问题中的Bonan ),然后对照随机模式进行检查,以了解生成的像素是黑色还是白色。较暗的像素将始终为黑色,最亮的像素将始终为白色,以保留图像细节。
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.util.Random;
import javax.imageio.ImageIO;
public class DitherGrayscale {
private BufferedImage original;
private double[] threshold = { 0.25, 0.26, 0.27, 0.28, 0.29, 0.3, 0.31,
0.32, 0.33, 0.34, 0.35, 0.36, 0.37, 0.38, 0.39, 0.4, 0.41, 0.42,
0.43, 0.44, 0.45, 0.46, 0.47, 0.48, 0.49, 0.5, 0.51, 0.52, 0.53,
0.54, 0.55, 0.56, 0.57, 0.58, 0.59, 0.6, 0.61, 0.62, 0.63, 0.64,
0.65, 0.66, 0.67, 0.68, 0.69 };
public static void main(String[] args) {
DitherGrayscale d = new DitherGrayscale();
d.readOriginal();
d.dither();
}
private void readOriginal() {
File f = new File("original.jpg");
try {
original = ImageIO.read(f);
} catch (IOException e) {
e.printStackTrace();
}
}
private void dither() {
BufferedImage imRes = new BufferedImage(original.getWidth(),
original.getHeight(), BufferedImage.TYPE_INT_RGB);
Random rn = new Random();
for (int i = 0; i < original.getWidth(); i++) {
for (int j = 0; j < original.getHeight(); j++) {
int color = original.getRGB(i, j);
int red = (color >>> 16) & 0xFF;
int green = (color >>> 8) & 0xFF;
int blue = (color >>> 0) & 0xFF;
double lum = (red * 0.21f + green * 0.71f + blue * 0.07f) / 255;
if (lum <= threshold[rn.nextInt(threshold.length)]) {
imRes.setRGB(i, j, 0x000000);
} else {
imRes.setRGB(i, j, 0xFFFFFF);
}
}
}
try {
ImageIO.write(imRes, "PNG", new File("result.png"));
} catch (IOException e) {
e.printStackTrace();
}
}
}

其他例子:

也适用于全彩色图像:

lNl_~:H;:W;Nl;1N[{[{ri}W*]{2/{:+}%}:P~}H*]z{P}%:A;H{W{I2/A=J2/=210/[0X9EF]=I2%2*J2%+m>2%N}fI}fJ
95个字节:) 对于输入和输出,
它都使用ASCII PGM(P2)格式,没有注释行。
该方法非常基础:将2 * 2像素的平方相加,转换为0..4范围,然后使用4位的对应模式生成2 * 2黑白像素。
这也意味着宽度和高度必须相等。
样品:

仅有27个字节的随机算法:
lNl_~*:X;Nl;1N{ri256mr>N}X*
它使用相同的文件格式。
样品:

最后是一种混合方法:随机抖动,偏向棋盘格模式;44个字节:
lNl_~:H;:W;Nl;1NH{W{ri128_mr\IJ+2%*+>N}fI}fJ
样品:

我不确定我是否在这里重新发明轮子,但我认为它可能是独一无二的...
随机序列有限
纯随机抖动
来自Averroes答案的城市形象
该算法使用局部发光能量和归一化以保留特征的概念。然后,初始版本使用随机抖动在类似亮度的区域上产生抖动外观。但是,它在视觉上并不那么吸引人。为了解决这个问题,将有限的一组有限的随机序列映射到原始输入像素的亮度,并反复使用样本并反复产生抖动的背景。
import java.awt.image.BufferedImage;
import java.io.File;
import javax.imageio.ImageIO;
public class LocalisedEnergyDitherRepeatRandom {
static private final float EIGHT_BIT_DIVISOR=1.0F/256;
private static final int KERNEL_SIZE_DIV_2 =4;
private static final double JITTER_MULTIPLIER=0.01;
private static final double NO_VARIANCE_JITTER_MULTIPLIER=1.5;
private static final int RANDOM_SEQUENCE_LENGTH=10;
private static final int RANDOM_SEQUENCE_COUNT=20;
private static final boolean USE_RANDOM_SEQUENCES=true;
public static void main(String args[]) throws Exception
{
BufferedImage image = ImageIO.read(new File("data/dog.jpg"));
final int width = image.getWidth();
final int height = image.getHeight();
float[][][] yuvImage =convertToYUVImage(image);
float[][][] outputYUVImage = new float[width][height][3];
double circularKernelLumaMean,sum,jitter,outputLuma,variance,inputLuma;
int circularKernelPixelCount,y,x,kx,ky;
double[][] randomSequences = new double[RANDOM_SEQUENCE_COUNT][RANDOM_SEQUENCE_LENGTH];
int[] randomSequenceOffsets = new int[RANDOM_SEQUENCE_COUNT];
// Generate random sequences
for (y=0;y<RANDOM_SEQUENCE_COUNT;y++)
{
for (x=0;x<RANDOM_SEQUENCE_LENGTH;x++)
{
randomSequences[y][x]=Math.random();
}
}
for (y=0;y<height;y++)
{
for (x=0;x<width;x++)
{
circularKernelLumaMean=0;
sum=0;
circularKernelPixelCount=0;
/// calculate the mean of the localised surrounding pixels contained in
/// the area of a circle surrounding the pixel.
for (ky=y-KERNEL_SIZE_DIV_2;ky<y+KERNEL_SIZE_DIV_2;ky++)
{
if (ky>=0 && ky<height)
{
for (kx=x-KERNEL_SIZE_DIV_2;kx<x+KERNEL_SIZE_DIV_2;kx++)
{
if (kx>=0 && kx<width)
{
double distance= Math.sqrt((x-kx)*(x-kx)+(y-ky)*(y-ky));
if (distance<=KERNEL_SIZE_DIV_2)
{
sum+=yuvImage[kx][ky][0];
circularKernelPixelCount++;
}
}
}
}
}
circularKernelLumaMean=sum/circularKernelPixelCount;
/// calculate the variance of the localised surrounding pixels contained in
/// the area of a circle surrounding the pixel.
sum =0;
for (ky=y-KERNEL_SIZE_DIV_2;ky<y+KERNEL_SIZE_DIV_2;ky++)
{
if (ky>=0 && ky<height)
{
for (kx=x-KERNEL_SIZE_DIV_2;kx<x+KERNEL_SIZE_DIV_2;kx++)
{
if (kx>=0 && kx<width)
{
double distance= Math.sqrt((x-kx)*(x-kx)+(y-ky)*(y-ky));
if (distance<=KERNEL_SIZE_DIV_2)
{
sum+=Math.abs(yuvImage[kx][ky][0]-circularKernelLumaMean);
}
}
}
}
}
variance = sum/(circularKernelPixelCount-1);
if (variance==0)
{
// apply some jitter to ensure there are no large blocks of single colour
inputLuma=yuvImage[x][y][0];
jitter = Math.random()*NO_VARIANCE_JITTER_MULTIPLIER;
}
else
{
// normalise the pixel based on localised circular kernel
inputLuma = outputYUVImage[x][y][0]=(float) Math.min(1.0, Math.max(0,yuvImage[x][y][0]/(circularKernelLumaMean*2)));
jitter = Math.exp(variance)*JITTER_MULTIPLIER;
}
if (USE_RANDOM_SEQUENCES)
{
int sequenceIndex =(int) (yuvImage[x][y][0]*RANDOM_SEQUENCE_COUNT);
int sequenceOffset = (randomSequenceOffsets[sequenceIndex]++)%RANDOM_SEQUENCE_LENGTH;
outputLuma=inputLuma+randomSequences[sequenceIndex][sequenceOffset]*jitter*2-jitter;
}
else
{
outputLuma=inputLuma+Math.random()*jitter*2-jitter;
}
// convert 8bit luma to 2 bit luma
outputYUVImage[x][y][0]=outputLuma<0.5?0:1.0f;
}
}
renderYUV(image,outputYUVImage);
ImageIO.write(image, "png", new File("data/dog.jpg.out.png"));
}
/**
* Converts the given buffered image into a 3-dimensional array of YUV pixels
* @param image the buffered image to convert
* @return 3-dimensional array of YUV pixels
*/
static private float[][][] convertToYUVImage(BufferedImage image)
{
final int width = image.getWidth();
final int height = image.getHeight();
float[][][] yuv = new float[width][height][3];
for (int y=0;y<height;y++)
{
for (int x=0;x<width;x++)
{
int rgb = image.getRGB( x, y );
yuv[x][y]=rgb2yuv(rgb);
}
}
return yuv;
}
/**
* Renders the given YUV image into the given buffered image.
* @param image the buffered image to render to
* @param pixels the YUV image to render.
* @param dimension the
*/
static private void renderYUV(BufferedImage image, float[][][] pixels)
{
final int height = image.getHeight();
final int width = image.getWidth();
int rgb;
for (int y=0;y<height;y++)
{
for (int x=0;x<width;x++)
{
rgb = yuv2rgb( pixels[x][y] );
image.setRGB( x, y,rgb );
}
}
}
/**
* Converts a RGB pixel into a YUV pixel
* @param rgb a pixel encoded as 24 bit RGB
* @return array representing a pixel. Consisting of Y,U and V components
*/
static float[] rgb2yuv(int rgb)
{
float red = EIGHT_BIT_DIVISOR*((rgb>>16)&0xFF);
float green = EIGHT_BIT_DIVISOR*((rgb>>8)&0xFF);
float blue = EIGHT_BIT_DIVISOR*(rgb&0xFF);
float Y = 0.299F*red + 0.587F * green + 0.114F * blue;
float U = (blue-Y)*0.565F;
float V = (red-Y)*0.713F;
return new float[]{Y,U,V};
}
/**
* Converts a YUV pixel into a RGB pixel
* @param yuv array representing a pixel. Consisting of Y,U and V components
* @return a pixel encoded as 24 bit RGB
*/
static int yuv2rgb(float[] yuv)
{
int red = (int) ((yuv[0]+1.403*yuv[2])*256);
int green = (int) ((yuv[0]-0.344 *yuv[1]- 0.714*yuv[2])*256);
int blue = (int) ((yuv[0]+1.77*yuv[1])*256);
// clamp to 0-255 range
red=red<0?0:red>255?255:red;
green=green<0?0:green>255?255:green;
blue=blue<0?0:blue>255?255:blue;
return (red<<16) | (green<<8) | blue;
}
}
想法如下:将图像分成多个n x n图块。我们计算这些瓷砖的平均颜色。然后我们将颜色范围映射0 - 255到0 - n*n给我们一个新值的范围v。然后,我们将该图块中的所有像素着色为黑色,并将v该图块中的像素随机着色为白色。它远非最佳,但仍可为我们带来可识别的结果。根据分辨率,通常在n=2或时效果最佳n=3。虽然在其中,n=2您已经可以从'模拟的颜色深度中找到伪像,以防万一n=3它可能已变得有些模糊。我假设图像应该保持相同的大小,但是您当然也可以使用此方法,只是将生成的图像的大小加倍/三倍以获取更多细节。
PS:我知道我参加聚会有点晚了,我记得当挑战开始时我没有任何想法,但是现在才有了脑波=)
from PIL import Image
import random
im = Image.open("dog.jpg") #Can be many different formats.
new = im.copy()
pix = im.load()
newpix = new.load()
width,height=im.size
print([width,height])
print(pix[1,1])
window = 3 # input parameter 'n'
area = window*window
for i in range(width//window): #loop over pixels
for j in range(height//window):#loop over pixels
avg = 0
area_pix = []
for k in range(window):
for l in range(window):
area_pix.append((k,l))#make a list of coordinates within the tile
try:
avg += pix[window*i+k,window*j+l][0]
newpix[window*i+k,window*j+l] = (0,0,0) #set everything to black
except IndexError:
avg += 255/2 #just an arbitrary mean value (when were outside of the image)
# this is just a dirty trick for coping with images that have
# sides that are not multiples of window
avg = avg/area
# val = v is the number of pixels within the tile that will be turned white
val = round(avg/255 * (area+0.99) - 0.5)#0.99 due to rounding errors
assert val<=area,'something went wrong with the val'
print(val)
random.shuffle(area_pix) #randomize pixel coordinates
for m in range(val):
rel_coords = area_pix.pop()#find random pixel within tile and turn it white
newpix[window*i+rel_coords[0],window*j+rel_coords[1]] = (255,255,255)
new.save('dog_dithered'+str(window)+'.jpg')
n=2:
n=3:
您想要的任何文件格式都可以。
让我们为这个问题定义一个非常紧凑的理论文件格式,因为任何现有的文件格式都有太多的开销来写一个快速答案。
让图像文件的前四个字节分别定义图像的宽度和高度(以像素为单位):
00000001 00101100 00000001 00101100
width: 300 px height: 300 px
随后是w * h0到255灰度值的字节:
10000101 10100101 10111110 11000110 ... [89,996 more bytes]
然后,我们可以用Python定义一段代码(145字节),以获取该图像并执行以下操作:
import random
f=open("a","rb");g=open("b","wb");g.write(f.read(4))
for i in f.read():g.write('\xff' if ord(i)>random.randint(0,255) else '\x00')
通过返回白色或黑色(其概率等于该像素的灰度值)来“抖动”。
应用于示例图像,它给出了类似以下内容:

它虽然不太漂亮,但是在预览中按比例缩小时看起来确实很相似,对于只有145字节的Python,我认为您不会变得更好。
接受24位或32位PNG / BMP文件(JPG产生的输出中带有一些灰色)。它也可以扩展到包含颜色的文件。
它使用速度优化的ELA将图像抖动为3位颜色,当给定测试图像时,它将返回为黑白。
我是否提到过它真的很快?
use System.Drawing
use System.Drawing.Imaging
use System.Runtime.InteropServices
use System.IO
class BW_Dither
var path as String = Directory.getCurrentDirectory to !
var rng = Random()
def main
file as String = Console.readLine to !
image as Bitmap = Bitmap(.path+"\\"+file)
image = .dither(image)
image.save(.path+"\\test\\image.png")
def dither(image as Bitmap) as Bitmap
output as Bitmap = Bitmap(image)
layers as Bitmap[] = @[ Bitmap(image), Bitmap(image), Bitmap(image),
Bitmap(image), Bitmap(image), Bitmap(image),
Bitmap(image)]
layers[0].rotateFlip(RotateFlipType.RotateNoneFlipX)
layers[1].rotateFlip(RotateFlipType.RotateNoneFlipY)
layers[2].rotateFlip(RotateFlipType.Rotate90FlipX)
layers[3].rotateFlip(RotateFlipType.Rotate90FlipY)
layers[4].rotateFlip(RotateFlipType.Rotate90FlipNone)
layers[5].rotateFlip(RotateFlipType.Rotate180FlipNone)
layers[6].rotateFlip(RotateFlipType.Rotate270FlipNone)
for i in layers.length, layers[i] = .dither_ela(layers[i])
layers[0].rotateFlip(RotateFlipType.RotateNoneFlipX)
layers[1].rotateFlip(RotateFlipType.RotateNoneFlipY)
layers[2].rotateFlip(RotateFlipType.Rotate270FlipY)
layers[3].rotateFlip(RotateFlipType.Rotate270FlipX)
layers[4].rotateFlip(RotateFlipType.Rotate270FlipNone)
layers[5].rotateFlip(RotateFlipType.Rotate180FlipNone)
layers[6].rotateFlip(RotateFlipType.Rotate90FlipNone)
vals = List<of List<of uint8[]>>()
data as List<of uint8[]> = .getData(output)
for l in layers, vals.add(.getData(l))
for i in data.count, for c in 3
x as int = 0
for n in vals.count, if vals[n][i][c] == 255, x += 1
if x > 3.5, data[i][c] = 255 to uint8
if x < 3.5, data[i][c] = 0 to uint8
.setData(output, data)
return output
def dither_ela(image as Bitmap) as Bitmap
error as decimal[] = @[0d, 0d, 0d]
rectangle as Rectangle = Rectangle(0, 0, image.width, image.height)
image_data as BitmapData = image.lockBits(rectangle, ImageLockMode.ReadWrite, image.pixelFormat) to !
pointer as IntPtr = image_data.scan0
bytes as uint8[] = uint8[](image_data.stride * image.height)
pfs as int = Image.getPixelFormatSize(image.pixelFormat) // 8
Marshal.copy(pointer, bytes, 0, image_data.stride * image.height)
for y as int in image.height, for x as int in image.width
position as int = (y * image_data.stride) + (x * pfs)
for i in 3
error[i] -= bytes[position + i]
if Math.abs(error[i] + 255 - bytes[position + i]) < Math.abs(error[i] - bytes[position + i])
bytes[position + i] = 255 to uint8
error[i] += 255
else, bytes[position + i] = 0 to uint8
Marshal.copy(bytes, 0, pointer, image_data.stride * image.height)
image.unlockBits(image_data)
return image
def getData(image as Bitmap) as List<of uint8[]>
rectangle as Rectangle = Rectangle(0, 0, image.width, image.height)
image_data as BitmapData = image.lockBits(rectangle, ImageLockMode.ReadOnly, image.pixelFormat) to !
pointer as IntPtr = image_data.scan0
bytes as uint8[] = uint8[](image_data.stride * image.height)
pixels as List<of uint8[]> = List<of uint8[]>()
for i in image.width * image.height, pixels.add(nil)
pfs as int = Image.getPixelFormatSize(image.pixelFormat) // 8
Marshal.copy(pointer, bytes, 0, image_data.stride * image.height)
count as int = 0
for y as int in image.height, for x as int in image.width
position as int = (y * image_data.stride) + (x * pfs)
if pfs == 4, alpha as uint8 = bytes[position + 3]
else, alpha as uint8 = 255
pixels[count] = @[
bytes[position + 2], #red
bytes[position + 1], #green
bytes[position], #blue
alpha] #alpha
count += 1
image.unlockBits(image_data)
return pixels
def setData(image as Bitmap, pixels as List<of uint8[]>)
if pixels.count <> image.width * image.height, throw Exception()
rectangle as Rectangle = Rectangle(0, 0, image.width, image.height)
image_data as BitmapData = image.lockBits(rectangle, ImageLockMode.ReadWrite, image.pixelFormat) to !
pointer as IntPtr = image_data.scan0
bytes as uint8[] = uint8[](image_data.stride * image.height)
pfs as int = Image.getPixelFormatSize(image.pixelFormat) // 8
Marshal.copy(pointer, bytes, 0, image_data.stride * image.height)
count as int = 0
for y as int in image.height, for x as int in image.width
position as int = (y * image_data.stride) + (x * pfs)
if pfs == 4, bytes[position + 3] = pixels[count][3] #alpha
bytes[position + 2] = pixels[count][0] #red
bytes[position + 1] = pixels[count][1] #green
bytes[position] = pixels[count][2] #blue
count += 1
Marshal.copy(bytes, 0, pointer, image_data.stride * image.height)
image.unlockBits(image_data)


col,并image.setPixel(x,y,col)一直保留到最后?
使用PNGJ和噪声加法加基本扩散的低级代码。此实现需要灰度8位PNG源。
import java.io.File;
import java.util.Random;
import ar.com.hjg.pngj.ImageInfo;
import ar.com.hjg.pngj.ImageLineInt;
import ar.com.hjg.pngj.PngReaderInt;
import ar.com.hjg.pngj.PngWriter;
public class Dither {
private static void dither1(File file1, File file2) {
PngReaderInt reader = new PngReaderInt(file1);
ImageInfo info = reader.imgInfo;
if( info.bitDepth != 8 && !info.greyscale ) throw new RuntimeException("only 8bits grayscale valid");
PngWriter writer = new PngWriter(file2, reader.imgInfo);
ImageLineInt line2 = new ImageLineInt(info);
int[] y = line2.getScanline();
int[] ye = new int[info.cols];
int ex = 0;
Random rand = new Random();
while(reader.hasMoreRows()) {
int[] x = reader.readRowInt().getScanline();
for( int i = 0; i < info.cols; i++ ) {
int t = x[i] + ex + ye[i];
y[i] = t > rand.nextInt(256) ? 255 : 0;
ex = (t - y[i]) / 2;
ye[i] = ex / 2;
}
writer.writeRow(line2);
}
reader.end();
writer.end();
}
public static void main(String[] args) {
dither1(new File(args[0]), new File(args[1]));
System.out.println("See output in " + args[1]);
}
}
(加 如果要尝试,请将此jar到您的构建路径中)。

另外一个好处是:这在内存使用方面非常高效(它仅存储三行),因此可以用于巨大的图像。
只是一个基于RNG的简单算法,再加上一些用于处理彩色图像的逻辑。有概率b将给定像素设置为白色的,否则将其设置为黑色;其中b是该像素的原始亮度。
import java.awt.Color;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.util.Random;
import java.util.Scanner;
import javax.imageio.ImageIO;
public class Ditherizer {
public static void main(String[]a) throws IOException{
Scanner input=new Scanner(System.in);
Random rand=new Random();
BufferedImage img=ImageIO.read(new File(input.nextLine()));
for(int x=0;x<img.getWidth();x++){
for(int y=0;y<img.getHeight();y++){
Color c=new Color(img.getRGB(x, y));
int r=c.getRed(),g=c.getGreen(),b=c.getBlue();
double bright=(r==g&&g==b)?r:Math.sqrt(0.299*r*r+0.587*g*g+0.114*b*b);
img.setRGB(x,y,(rand.nextInt(256)>bright?Color.BLACK:Color.WHITE).getRGB());
}
}
ImageIO.write(img,"jpg",new File(input.nextLine()));
input.close();
}
}
这是狗图像的潜在结果: