Skip to content

Instantly share code, notes, and snippets.

@CocoaBob
Created January 21, 2023 04:46
Show Gist options
  • Save CocoaBob/7eff06420a71a235e17d4ae7292a8202 to your computer and use it in GitHub Desktop.
Save CocoaBob/7eff06420a71a235e17d4ae7292a8202 to your computer and use it in GitHub Desktop.
Get UIImage from PCX data, supports both monochrome and color formats, need to install `pod "BinaryDataScanner", '1.0.3'`. Ref: https://github.com/actumn/playground/tree/master/swift/swift_load_PCX
import Foundation
import UIKit
struct PCXhead {
var identity: UInt8 = 0
var pcxver: UInt8 = 0
var compression: UInt8 = 0
var bpp: UInt8 = 0
var minX: UInt16 = 0
var minY: UInt16 = 0
var maxX: UInt16 = 0
var maxY: UInt16 = 0
var hDPI: UInt16 = 0
var vDPI: UInt16 = 0
var colormap: Data? // 16 * 3
var reserved1: UInt8 = 0
var np: UInt8 = 0
var bpl: UInt16 = 0
var pallinfo: UInt16 = 0
var width: UInt16 = 0
var height: UInt16 = 0
var reserved2: Data? // 54
}
public struct PixelData {
var r: UInt8
var g: UInt8
var b: UInt8
var a: UInt8 = 255
}
private let rgbColorSpace = CGColorSpaceCreateDeviceRGB()
private let bitmapInfo:CGBitmapInfo = CGBitmapInfo(rawValue: CGImageAlphaInfo.premultipliedLast.rawValue)
public func imageFromARGB32Bitmap(pixels:[PixelData], width: Int, height: Int) -> UIImage? {
let bitsPerComponent: Int = 8
let bitsPerPixel: Int = 32
assert(pixels.count == Int(width * height))
var data = pixels
let providerRef = CGDataProvider(data: NSData(bytes: &data, length: data.count * MemoryLayout<PixelData>.size))!
if let cgImage = CGImage(width: width,
height: height,
bitsPerComponent: bitsPerComponent,
bitsPerPixel: bitsPerPixel,
bytesPerRow: width * MemoryLayout<PixelData>.size,
space: rgbColorSpace,
bitmapInfo: bitmapInfo,
provider: providerRef,
decode: nil,
shouldInterpolate: true,
intent: CGColorRenderingIntent.defaultIntent) {
return UIImage(cgImage: cgImage)
}
return nil
}
func pcxDataToImage(rawData: Data) -> UIImage? {
let data = BinaryDataScanner(data: rawData, endian: CFByteOrderLittleEndian)
var bytes = [UInt8]()
var palettes = [PixelData]()
var pixels = [PixelData]()
do {
let pcxHead = try PCXhead(
identity : data.read8(),
pcxver : data.read8(),
compression : data.read8(),
bpp : data.read8(), // The number of bits constituting one plane. Most often 1, 2, 4 or 8
minX : data.read16(), // The minimum x co-ordinate of the image position
minY : data.read16(), // The minimum y co-ordinate of the image position
maxX : data.read16(), // The maximum x co-ordinate of the image position
maxY : data.read16(), // The maximum y co-ordinate of the image position
hDPI : data.read16(), // The horizontal image resolution in DPI
vDPI : data.read16(), // The vertical image resolution in DPI
colormap : data.data(count: 48),// The EGA palette for 16-color images
reserved1 : data.read8(), // The first reserved field, usually set to zero
np : data.read8(), // The number of color planes constituting the pixel data. Mostly chosen to be 1, 3, or 4
bpl : data.read16(), // The number of bytes of one color plane representing a single scan line
pallinfo : data.read16(), // The mode in which to construe the palette,
// 1: The palette contains monochrome or color information
// 2: The palette contains grayscale information
width : data.read16(), // The horizontal resolution of the source system's screen
height : data.read16(), // The vertical resolution of the source system's screen
reserved2 : data.data(count: 54)// The second reserved field, intended for future extensions, and usually set to zero bytes
)
let rowSize = Int(pcxHead.bpl) * Int(pcxHead.np)
let xInit = Int(pcxHead.minX)
let yInit = Int(pcxHead.minY)
let width = Int(pcxHead.maxX - pcxHead.minX + 1)
let height = Int(pcxHead.maxY - pcxHead.minY + 1)
var tx = xInit
var ty = yInit
var byte: UInt8 // for data.read8()
var runlen: Int // for run-length encoding
var drawing = true
while (drawing) {
byte = try data.read8()
/* check for run */
if((byte & 0xc0) == 0xc0) {
runlen = Int(byte & 0x3f)
byte = try data.read8()
} else {
runlen = 1
}
while( drawing && runlen > 0 ) {
if pcxHead.bpp == 1 {
for i in 0..<8 {
if (tx * 8 + i) < width {
let bit = ((byte >> (7-i)) & 1)
bytes.append(bit * 255)
}
}
tx += 1
} else {
if ( tx < width * 4 ) {
bytes.append(byte)
}
tx += 1
}
runlen -= 1
if ( tx >= rowSize ) {
tx = xInit
ty += 1
runlen = 0
}
if ( ty >= height ) {
drawing = false
}
}
}
/* Setting Palette */
let hasPalette = try? data.read8()
if ( hasPalette == 12 ) {
for _ in 0 ..< 256 {
let r = try data.read8()
let g = try data.read8()
let b = try data.read8()
palettes.append(PixelData(r: r, g: g, b: b))
}
}
if pcxHead.np == 1 {
if palettes.isEmpty {
for byte in bytes {
pixels.append(PixelData(r: byte, g: byte, b: byte))
}
} else {
for i in bytes {
pixels.append(palettes[Int(i)])
}
}
} else if pcxHead.np == 3 {
var pos: Int = 0
while pos < bytes.count {
for i in 0..<min(Int(pcxHead.bpl), width) {
pixels.append(PixelData(r: bytes[pos + i],
g: bytes[pos + i + Int(pcxHead.bpl)],
b: bytes[pos + i + Int(pcxHead.bpl) * 2],
a: 255))
}
pos += rowSize
}
} else if pcxHead.np == 4 {
var pos: Int = 0
while pos < bytes.count {
for i in 0..<min(Int(pcxHead.bpl), width) {
pixels.append(PixelData(r: bytes[pos + i],
g: bytes[pos + i + Int(pcxHead.bpl)],
b: bytes[pos + i + Int(pcxHead.bpl) * 2],
a: bytes[pos + i + Int(pcxHead.bpl) * 3]))
}
pos += rowSize
}
}
return imageFromARGB32Bitmap(pixels: pixels, width: width, height: height)
} catch let e {
print(e)
return nil
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment