我正在尝试以 1 位单色显示图像的预览,例如,不是灰度,而是黑白双色。它应该指示图像在传真后的外观。低至每像素 1 位的格式在 OS X 上不可用,只有 8 位灰度。有没有办法使用 Core Graphics 或其他框架(理想情况下使用抖动)来实现这种效果?
我知道有一个名为 CIColorMonochrome 的过滤器,但这只会将图像转换为灰度。
我正在尝试以 1 位单色显示图像的预览,例如,不是灰度,而是黑白双色。它应该指示图像在传真后的外观。低至每像素 1 位的格式在 OS X 上不可用,只有 8 位灰度。有没有办法使用 Core Graphics 或其他框架(理想情况下使用抖动)来实现这种效果?
我知道有一个名为 CIColorMonochrome 的过滤器,但这只会将图像转换为灰度。
AFAIK 不支持创建 1 位深度的 NSImageRep(以及在 CG 世界中),因此我们必须手动进行。使用 CIImage 完成此任务可能很有用。在这里,我采用经典(您可以称之为老式)方式。这是一个代码,显示了我们如何做到这一点。首先从 NSImageRep 创建一个灰色图像,因此无论源图像将被格式化(也可以是 PDF 文件),我们都有一个定义明确且简单的格式。生成的灰度图像是双色调图像的来源。这是创建灰度图像的代码:(不size / resolution
考虑源图像,只计算像素!):
- (NSBitmapImageRep *) grayRepresentationOf:(NSImageRep *)aRep
{
NSBitmapImageRep *newRep =
[[NSBitmapImageRep alloc] initWithBitmapDataPlanes:NULL
pixelsWide:[aRep pixelsWide]
pixelsHigh:[aRep pixelsHigh]
bitsPerSample:8
samplesPerPixel:1
hasAlpha:NO //must be NO !
isPlanar:NO
colorSpaceName:NSCalibratedWhiteColorSpace
bytesPerRow:0
bitsPerPixel:0 ];
// this new imagerep has (as default) a resolution of 72 dpi
[NSGraphicsContext saveGraphicsState];
NSGraphicsContext *context = [NSGraphicsContext graphicsContextWithBitmapImageRep:newRep];
if( context==nil ){
NSLog( @"*** %s context is nil", __FUNCTION__ );
return nil;
}
[NSGraphicsContext setCurrentContext:context];
[aRep drawInRect:NSMakeRect( 0, 0, [newRep pixelsWide], [newRep pixelsHigh] )];
[NSGraphicsContext restoreGraphicsState];
return [newRep autorelease];
}
在下一个方法中,我们从给定的 NSImageRep(它的一个子类)创建一个 NXBitmapImageRep(每像素位数=1,每像素样本=1),并将使用刚刚给出的方法:
- (NSBitmapImageRep *) binaryRepresentationOf:(NSImageRep *)aRep
{
NSBitmapImageRep *grayRep = [aRep grayRepresentation];
if( grayRep==nil ) return nil;
NSInteger numberOfRows = [grayRep pixelsHigh];
NSInteger numberOfCols = [grayRep pixelsWide];
NSBitmapImageRep *newRep =
[[NSBitmapImageRep alloc] initWithBitmapDataPlanes:NULL
pixelsWide:numberOfCols
pixelsHigh:numberOfRows
bitsPerSample:1
samplesPerPixel:1
hasAlpha:NO
isPlanar:NO
colorSpaceName:NSCalibratedWhiteColorSpace
bitmapFormat:0
bytesPerRow:0
bitsPerPixel:0 ];
unsigned char *bitmapDataSource = [grayRep bitmapData];
unsigned char *bitmapDataDest = [newRep bitmapData];
// here is the place to use dithering or error diffusion (code below)
// iterate over all pixels
NSInteger grayBPR = [grayRep bytesPerRow];
NSInteger binBPR = [newRep bytesPerRow];
NSInteger pWide = [newRep pixelsWide];
for( NSInteger row=0; row<numberOfRows; row++ ){
unsigned char *rowDataSource = bitmapDataSource + row*grayBPR;
unsigned char *rowDataDest = bitmapDataDest + row*binBPR;
NSInteger destCol = 0;
unsigned char bw = 0;
for( NSInteger col = 0; col<pWide; ){
unsigned char gray = rowDataSource[col];
if( gray>127 ) {bw |= (1<<(7-col%8)); };
col++;
if( (col%8 == 0) || (col==pWide) ){
rowDataDest[destCol] = bw;
bw = 0;
destCol++;
}
}
}
// save as PNG for testing and return
[[newRep representationUsingType:NSPNGFileType properties:nil] writeToFile:@"/tmp/bin_1.png" atomically:YES];
return [newRep autorelease];
}
对于误差扩散,我使用以下代码直接更改灰度图像的位图。这是允许的,因为不再使用灰色图像本身。
// change bitmapDataSource : use Error-Diffusion
for( NSInteger row=0; row<numberOfRows-1; row++ ){
unsigned char *currentRowData = bitmapDataSource + row*grayBPR;
unsigned char *nextRowData = currentRowData + grayBPR;
for( NSInteger col = 1; col<numberOfCols; col++ ){
NSInteger origValue = currentRowData[col];
NSInteger newValue = (origValue>127) ? 255 : 0;
NSInteger error = -(newValue - origValue);
currentRowData[col] = newValue;
currentRowData[col+1] = clamp(currentRowData[col+1] + (7*error/16));
nextRowData[col-1] = clamp( nextRowData[col-1] + (3*error/16) );
nextRowData[col] = clamp( nextRowData[col] + (5*error/16) );
nextRowData[col+1] = clamp( nextRowData[col+1] + (error/16) );
}
}
钳位是在方法之前定义的宏
#define clamp(z) ( (z>255)?255 : ((z<0)?0:z) )
这使得 unsigned char 字节具有有效值 (0<=z<=255)