netpbm.odin 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731
  1. #+vet !using-stmt
  2. package netpbm
  3. import "core:bytes"
  4. import "core:fmt"
  5. import "core:image"
  6. import "core:mem"
  7. import "core:strconv"
  8. import "core:strings"
  9. import "core:unicode"
  10. import "base:runtime"
  11. Image :: image.Image
  12. Format :: image.Netpbm_Format
  13. Header :: image.Netpbm_Header
  14. Info :: image.Netpbm_Info
  15. Error :: image.Error
  16. Format_Error :: image.Netpbm_Error
  17. Formats :: bit_set[Format]
  18. PBM :: Formats{.P1, .P4}
  19. PGM :: Formats{.P2, .P5}
  20. PPM :: Formats{.P3, .P6}
  21. PNM :: PBM + PGM + PPM
  22. PAM :: Formats{.P7}
  23. PFM :: Formats{.Pf, .PF}
  24. ASCII :: Formats{.P1, .P2, .P3}
  25. BINARY :: Formats{.P4, .P5, .P6} + PAM + PFM
  26. load_from_bytes :: proc(data: []byte, allocator := context.allocator) -> (img: ^Image, err: Error) {
  27. context.allocator = allocator
  28. img = new(Image)
  29. img.which = .NetPBM
  30. header: Header; defer header_destroy(&header)
  31. header_size: int
  32. header, header_size = parse_header(data) or_return
  33. img_data := data[header_size:]
  34. decode_image(img, header, img_data) or_return
  35. info := new(Info)
  36. info.header = header
  37. if header.format == .P7 && header.tupltype != "" {
  38. info.header.tupltype = strings.clone(header.tupltype)
  39. }
  40. img.metadata = info
  41. return img, nil
  42. }
  43. save_to_buffer :: proc(img: ^Image, custom_info: Info = {}, allocator := context.allocator) -> (buffer: []byte, err: Error) {
  44. context.allocator = allocator
  45. info: Info = {}
  46. if custom_info.header.width > 0 {
  47. // Custom info has been set, use it.
  48. info = custom_info
  49. } else {
  50. img_info, ok := img.metadata.(^image.Netpbm_Info)
  51. if !ok {
  52. // image doesn't have .Netpbm info, guess it
  53. auto_info, auto_info_found := autoselect_pbm_format_from_image(img)
  54. if auto_info_found {
  55. info = auto_info
  56. } else {
  57. return {}, .Invalid_Input_Image
  58. }
  59. } else {
  60. // use info as stored on image
  61. info = img_info^
  62. }
  63. }
  64. // using info so we can just talk about the header
  65. using info
  66. // validation
  67. if header.format in (PBM + PGM + Formats{.Pf}) && img.channels != 1 \
  68. || header.format in (PPM + Formats{.PF}) && img.channels != 3 {
  69. err = .Invalid_Number_Of_Channels
  70. return
  71. }
  72. if header.format in (PNM + PAM) {
  73. if header.maxval <= int(max(u8)) && img.depth != 8 \
  74. || header.maxval > int(max(u8)) && header.maxval <= int(max(u16)) && img.depth != 16 {
  75. err = .Invalid_Image_Depth
  76. return
  77. }
  78. } else if header.format in PFM && img.depth != 32 {
  79. err = .Invalid_Image_Depth
  80. return
  81. }
  82. // we will write to a string builder
  83. data: strings.Builder
  84. strings.builder_init(&data)
  85. // all PNM headers start with the format
  86. fmt.sbprintf(&data, "%s\n", header.format)
  87. if header.format in PNM {
  88. fmt.sbprintf(&data, "%i %i\n", img.width, img.height)
  89. if header.format not_in PBM {
  90. fmt.sbprintf(&data, "%i\n", header.maxval)
  91. }
  92. } else if header.format in PAM {
  93. if len(header.tupltype) > 0 {
  94. fmt.sbprintf(&data, "WIDTH %i\nHEIGHT %i\nMAXVAL %i\nDEPTH %i\nTUPLTYPE %s\nENDHDR\n",
  95. img.width, img.height, header.maxval, img.channels, header.tupltype)
  96. } else {
  97. fmt.sbprintf(&data, "WIDTH %i\nHEIGHT %i\nMAXVAL %i\nDEPTH %i\nENDHDR\n",
  98. img.width, img.height, header.maxval, img.channels)
  99. }
  100. } else if header.format in PFM {
  101. scale := -header.scale if header.little_endian else header.scale
  102. fmt.sbprintf(&data, "%i %i\n%f\n", img.width, img.height, scale)
  103. }
  104. switch header.format {
  105. // Compressed binary
  106. case .P4:
  107. header_buf := data.buf[:]
  108. pixels := img.pixels.buf[:]
  109. p4_buffer_size := (img.width / 8 + 1) * img.height
  110. reserve(&data.buf, len(header_buf) + p4_buffer_size)
  111. // we build up a byte value until it is completely filled
  112. // or we reach the end the row
  113. for y in 0 ..< img.height {
  114. b: byte
  115. for x in 0 ..< img.width {
  116. i := y * img.width + x
  117. bit := byte(7 - (x % 8))
  118. v : byte = 0 if pixels[i] == 0 else 1
  119. b |= (v << bit)
  120. if bit == 0 {
  121. append(&data.buf, b)
  122. b = 0
  123. }
  124. }
  125. if b != 0 {
  126. append(&data.buf, b)
  127. b = 0
  128. }
  129. }
  130. // Simple binary
  131. case .P5, .P6, .P7, .Pf, .PF:
  132. header_buf := data.buf[:]
  133. pixels := img.pixels.buf[:]
  134. resize(&data.buf, len(header_buf) + len(pixels))
  135. mem.copy(raw_data(data.buf[len(header_buf):]), raw_data(pixels), len(pixels))
  136. // convert from native endianness
  137. if img.depth == 16 {
  138. pixels := mem.slice_data_cast([]u16be, data.buf[len(header_buf):])
  139. for &p in pixels {
  140. p = u16be(transmute(u16) p)
  141. }
  142. } else if header.format in PFM {
  143. if header.little_endian {
  144. pixels := mem.slice_data_cast([]f32le, data.buf[len(header_buf):])
  145. for &p in pixels {
  146. p = f32le(transmute(f32) p)
  147. }
  148. } else {
  149. pixels := mem.slice_data_cast([]f32be, data.buf[len(header_buf):])
  150. for &p in pixels {
  151. p = f32be(transmute(f32) p)
  152. }
  153. }
  154. }
  155. // If-it-looks-like-a-bitmap ASCII
  156. case .P1:
  157. pixels := img.pixels.buf[:]
  158. for y in 0 ..< img.height {
  159. for x in 0 ..< img.width {
  160. i := y * img.width + x
  161. append(&data.buf, '0' if pixels[i] == 0 else '1')
  162. }
  163. append(&data.buf, '\n')
  164. }
  165. // Token ASCII
  166. case .P2, .P3:
  167. switch img.depth {
  168. case 8:
  169. pixels := img.pixels.buf[:]
  170. for y in 0 ..< img.height {
  171. for x in 0 ..< img.width {
  172. i := y * img.width + x
  173. for c in 0 ..< img.channels {
  174. j := i * img.channels + c
  175. fmt.sbprintf(&data, "%i ", pixels[j])
  176. }
  177. fmt.sbprint(&data, "\n")
  178. }
  179. fmt.sbprint(&data, "\n")
  180. }
  181. case 16:
  182. pixels := mem.slice_data_cast([]u16, img.pixels.buf[:])
  183. for y in 0 ..< img.height {
  184. for x in 0 ..< img.width {
  185. i := y * img.width + x
  186. for c in 0 ..< img.channels {
  187. j := i * img.channels + c
  188. fmt.sbprintf(&data, "%i ", pixels[j])
  189. }
  190. fmt.sbprint(&data, "\n")
  191. }
  192. fmt.sbprint(&data, "\n")
  193. }
  194. case:
  195. return data.buf[:], .Invalid_Image_Depth
  196. }
  197. case:
  198. return data.buf[:], .Invalid_Format
  199. }
  200. return data.buf[:], Format_Error.None
  201. }
  202. parse_header :: proc(data: []byte, allocator := context.allocator) -> (header: Header, length: int, err: Error) {
  203. context.allocator = allocator
  204. // we need the signature and a space
  205. if len(data) < 3 {
  206. err = Format_Error.Incomplete_Header
  207. return
  208. }
  209. if data[0] == 'P' {
  210. switch data[1] {
  211. case '1' ..= '6':
  212. return _parse_header_pnm(data)
  213. case '7':
  214. return _parse_header_pam(data, allocator)
  215. case 'F', 'f':
  216. return _parse_header_pfm(data)
  217. }
  218. }
  219. err = .Invalid_Signature
  220. return
  221. }
  222. @(private)
  223. _parse_header_pnm :: proc(data: []byte) -> (header: Header, length: int, err: Error) {
  224. SIG_LENGTH :: 2
  225. {
  226. header_formats := []Format{.P1, .P2, .P3, .P4, .P5, .P6}
  227. header.format = header_formats[data[1] - '0' - 1]
  228. }
  229. // have a list of fielda for easy iteration
  230. header_fields: []^int
  231. if header.format in PBM {
  232. header_fields = {&header.width, &header.height}
  233. header.maxval = 1 // we know maxval for a bitmap
  234. } else {
  235. header_fields = {&header.width, &header.height, &header.maxval}
  236. }
  237. // we're keeping track of the header byte length
  238. length = SIG_LENGTH
  239. // loop state
  240. in_comment := false
  241. already_in_space := true
  242. current_field := 0
  243. current_value := header_fields[0]
  244. parse_loop: for d in data[SIG_LENGTH:] {
  245. length += 1
  246. // handle comments
  247. if in_comment {
  248. switch d {
  249. // comments only go up to next carriage return or line feed
  250. case '\r', '\n':
  251. in_comment = false
  252. }
  253. continue
  254. } else if d == '#' {
  255. in_comment = true
  256. continue
  257. }
  258. // handle whitespace
  259. in_space := unicode.is_white_space(rune(d))
  260. if in_space {
  261. if already_in_space {
  262. continue
  263. }
  264. already_in_space = true
  265. // switch to next value
  266. current_field += 1
  267. if current_field == len(header_fields) {
  268. // header byte length is 1-index so we'll increment again
  269. length += 1
  270. break parse_loop
  271. }
  272. current_value = header_fields[current_field]
  273. } else {
  274. already_in_space = false
  275. if !unicode.is_digit(rune(d)) {
  276. err = Format_Error.Invalid_Header_Token_Character
  277. return
  278. }
  279. val := int(d - '0')
  280. current_value^ = current_value^ * 10 + val
  281. }
  282. }
  283. // set extra info
  284. header.channels = 3 if header.format in PPM else 1
  285. header.depth = 16 if header.maxval > int(max(u8)) else 8
  286. // limit checking
  287. if current_field < len(header_fields) {
  288. err = Format_Error.Incomplete_Header
  289. return
  290. }
  291. if header.width < 1 \
  292. || header.height < 1 \
  293. || header.maxval < 1 || header.maxval > int(max(u16)) {
  294. fmt.printf("[pnm] Header: {{width = %v, height = %v, maxval: %v}}\n", header.width, header.height, header.maxval)
  295. err = .Invalid_Header_Value
  296. return
  297. }
  298. length -= 1
  299. err = Format_Error.None
  300. return
  301. }
  302. @(private)
  303. _parse_header_pam :: proc(data: []byte, allocator := context.allocator) -> (header: Header, length: int, err: Error) {
  304. context.allocator = allocator
  305. // the spec needs the newline apparently
  306. if string(data[0:3]) != "P7\n" {
  307. err = .Invalid_Signature
  308. return
  309. }
  310. header.format = .P7
  311. SIGNATURE_LENGTH :: 3
  312. HEADER_END :: "ENDHDR\n"
  313. // we can already work out the size of the header
  314. header_end_index := strings.index(string(data), HEADER_END)
  315. if header_end_index == -1 {
  316. err = Format_Error.Incomplete_Header
  317. return
  318. }
  319. length = header_end_index + len(HEADER_END)
  320. runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = context.temp_allocator == allocator)
  321. // string buffer for the tupltype
  322. tupltype: strings.Builder
  323. strings.builder_init(&tupltype, context.temp_allocator); defer strings.builder_destroy(&tupltype)
  324. fmt.sbprint(&tupltype, "")
  325. // PAM uses actual lines, so we can iterate easily
  326. line_iterator := string(data[SIGNATURE_LENGTH : header_end_index])
  327. parse_loop: for line in strings.split_lines_iterator(&line_iterator) {
  328. line := line
  329. if len(line) == 0 || line[0] == '#' {
  330. continue
  331. }
  332. field, ok := strings.fields_iterator(&line)
  333. value := strings.trim_space(line)
  334. // the field will change, but the logic stays the same
  335. current_field: ^int
  336. switch field {
  337. case "WIDTH": current_field = &header.width
  338. case "HEIGHT": current_field = &header.height
  339. case "DEPTH": current_field = &header.channels
  340. case "MAXVAL": current_field = &header.maxval
  341. case "TUPLTYPE":
  342. if len(value) == 0 {
  343. err = .Invalid_Header_Value
  344. return
  345. }
  346. if len(tupltype.buf) == 0 {
  347. fmt.sbprint(&tupltype, value)
  348. } else {
  349. fmt.sbprint(&tupltype, "", value)
  350. }
  351. continue
  352. case:
  353. continue
  354. }
  355. if current_field^ != 0 {
  356. err = Format_Error.Duplicate_Header_Field
  357. return
  358. }
  359. current_field^, ok = strconv.parse_int(value)
  360. if !ok {
  361. err = Format_Error.Invalid_Header_Value
  362. return
  363. }
  364. }
  365. // extra info
  366. header.depth = 16 if header.maxval > int(max(u8)) else 8
  367. // limit checking
  368. if header.width < 1 \
  369. || header.height < 1 \
  370. || header.maxval < 1 \
  371. || header.maxval > int(max(u16)) {
  372. fmt.printf("[pam] Header: {{width = %v, height = %v, maxval: %v}}\n", header.width, header.height, header.maxval)
  373. err = Format_Error.Invalid_Header_Value
  374. return
  375. }
  376. header.tupltype = strings.clone(strings.to_string(tupltype))
  377. err = Format_Error.None
  378. return
  379. }
  380. @(private)
  381. _parse_header_pfm :: proc(data: []byte) -> (header: Header, length: int, err: Error) {
  382. // we can just cycle through tokens for PFM
  383. field_iterator := string(data)
  384. field, ok := strings.fields_iterator(&field_iterator)
  385. switch field {
  386. case "Pf":
  387. header.format = .Pf
  388. header.channels = 1
  389. case "PF":
  390. header.format = .PF
  391. header.channels = 3
  392. case:
  393. err = .Invalid_Signature
  394. return
  395. }
  396. // floating point
  397. header.depth = 32
  398. // width
  399. field, ok = strings.fields_iterator(&field_iterator)
  400. if !ok {
  401. err = Format_Error.Incomplete_Header
  402. return
  403. }
  404. header.width, ok = strconv.parse_int(field)
  405. if !ok {
  406. err = Format_Error.Invalid_Header_Value
  407. return
  408. }
  409. // height
  410. field, ok = strings.fields_iterator(&field_iterator)
  411. if !ok {
  412. err = Format_Error.Incomplete_Header
  413. return
  414. }
  415. header.height, ok = strconv.parse_int(field)
  416. if !ok {
  417. err = Format_Error.Invalid_Header_Value
  418. return
  419. }
  420. // scale (sign is endianness)
  421. field, ok = strings.fields_iterator(&field_iterator)
  422. if !ok {
  423. err = Format_Error.Incomplete_Header
  424. return
  425. }
  426. header.scale, ok = strconv.parse_f32(field)
  427. if !ok {
  428. err = Format_Error.Invalid_Header_Value
  429. return
  430. }
  431. if header.scale < 0.0 {
  432. header.little_endian = true
  433. header.scale = -header.scale
  434. }
  435. // pointer math to get header size
  436. length = int((uintptr(raw_data(field_iterator)) + 1) - uintptr(raw_data(data)))
  437. // limit checking
  438. if header.width < 1 \
  439. || header.height < 1 \
  440. || header.scale == 0.0 {
  441. fmt.printf("[pfm] Header: {{width = %v, height = %v, scale: %v}}\n", header.width, header.height, header.scale)
  442. err = .Invalid_Header_Value
  443. return
  444. }
  445. err = Format_Error.None
  446. return
  447. }
  448. decode_image :: proc(img: ^Image, header: Header, data: []byte, allocator := context.allocator) -> (err: Error) {
  449. assert(img != nil)
  450. context.allocator = allocator
  451. img.width = header.width
  452. img.height = header.height
  453. img.channels = header.channels
  454. img.depth = header.depth
  455. buffer_size := image.compute_buffer_size(img.width, img.height, img.channels, img.depth)
  456. // we can check data size for binary formats
  457. if header.format in BINARY {
  458. if len(data) < buffer_size {
  459. fmt.printf("len(data): %v, buffer size: %v\n", len(data), buffer_size)
  460. return .Buffer_Too_Small
  461. }
  462. }
  463. // for ASCII and P4, we use length for the termination condition, so start at 0
  464. // BINARY will be a simple memcopy so the buffer length should also be initialised
  465. if header.format in ASCII || header.format == .P4 {
  466. bytes.buffer_init_allocator(&img.pixels, 0, buffer_size)
  467. } else {
  468. bytes.buffer_init_allocator(&img.pixels, buffer_size, buffer_size)
  469. }
  470. switch header.format {
  471. // Compressed binary
  472. case .P4:
  473. for d in data {
  474. for b in 1 ..= 8 {
  475. bit := byte(8 - b)
  476. pix := (d >> bit) & 1
  477. bytes.buffer_write_byte(&img.pixels, pix)
  478. if len(img.pixels.buf) % img.width == 0 {
  479. break
  480. }
  481. }
  482. if len(img.pixels.buf) == cap(img.pixels.buf) {
  483. break
  484. }
  485. }
  486. // Simple binary
  487. case .P5, .P6, .P7, .Pf, .PF:
  488. copy(img.pixels.buf[:], data[:])
  489. // convert to native endianness
  490. if header.format in PFM {
  491. pixels := mem.slice_data_cast([]f32, img.pixels.buf[:])
  492. if header.little_endian {
  493. for &p in pixels {
  494. p = f32(transmute(f32le) p)
  495. }
  496. } else {
  497. for &p in pixels {
  498. p = f32(transmute(f32be) p)
  499. }
  500. }
  501. } else {
  502. if img.depth == 16 {
  503. pixels := mem.slice_data_cast([]u16, img.pixels.buf[:])
  504. for &p in pixels {
  505. p = u16(transmute(u16be) p)
  506. }
  507. }
  508. }
  509. // If-it-looks-like-a-bitmap ASCII
  510. case .P1:
  511. for c in data {
  512. switch c {
  513. case '0', '1':
  514. bytes.buffer_write_byte(&img.pixels, c - '0')
  515. }
  516. if len(img.pixels.buf) == cap(img.pixels.buf) {
  517. break
  518. }
  519. }
  520. if len(img.pixels.buf) < cap(img.pixels.buf) {
  521. err = Format_Error.Buffer_Too_Small
  522. return
  523. }
  524. // Token ASCII
  525. case .P2, .P3:
  526. field_iterator := string(data)
  527. for field in strings.fields_iterator(&field_iterator) {
  528. value, ok := strconv.parse_int(field)
  529. if !ok {
  530. err = Format_Error.Invalid_Buffer_ASCII_Token
  531. return
  532. }
  533. //? do we want to enforce the maxval, the limit, or neither
  534. if value > int(max(u16)) /*header.maxval*/ {
  535. err = Format_Error.Invalid_Buffer_Value
  536. return
  537. }
  538. switch img.depth {
  539. case 8:
  540. bytes.buffer_write_byte(&img.pixels, u8(value))
  541. case 16:
  542. vb := transmute([2]u8) u16(value)
  543. bytes.buffer_write(&img.pixels, vb[:])
  544. }
  545. if len(img.pixels.buf) == cap(img.pixels.buf) {
  546. break
  547. }
  548. }
  549. if len(img.pixels.buf) < cap(img.pixels.buf) {
  550. err = Format_Error.Buffer_Too_Small
  551. return
  552. }
  553. }
  554. err = Format_Error.None
  555. return
  556. }
  557. // Automatically try to select an appropriate format to save to based on `img.channel` and `img.depth`
  558. autoselect_pbm_format_from_image :: proc(img: ^Image, prefer_binary := true, force_black_and_white := false, pfm_scale := f32(1.0)) -> (res: Info, ok: bool) {
  559. /*
  560. PBM (P1, P4): Portable Bit Map, stores black and white images (1 channel)
  561. PGM (P2, P5): Portable Gray Map, stores greyscale images (1 channel, 1 or 2 bytes per value)
  562. PPM (P3, P6): Portable Pixel Map, stores colour images (3 channel, 1 or 2 bytes per value)
  563. PAM (P7 ): Portable Arbitrary Map, stores arbitrary channel images (1 or 2 bytes per value)
  564. PFM (Pf, PF): Portable Float Map, stores floating-point images (Pf: 1 channel, PF: 3 channel)
  565. ASCII :: Formats{.P1, .P2, .P3}
  566. */
  567. using res.header
  568. width = img.width
  569. height = img.height
  570. channels = img.channels
  571. depth = img.depth
  572. maxval = 255 if img.depth == 8 else 65535
  573. little_endian = true if ODIN_ENDIAN == .Little else false
  574. // Assume we'll find a suitable format
  575. ok = true
  576. switch img.channels {
  577. case 1:
  578. // Must be Portable Float Map
  579. if img.depth == 32 {
  580. format = .Pf
  581. return
  582. }
  583. if force_black_and_white {
  584. // Portable Bit Map
  585. format = .P4 if prefer_binary else .P1
  586. maxval = 1
  587. return
  588. } else {
  589. // Portable Gray Map
  590. format = .P5 if prefer_binary else .P2
  591. return
  592. }
  593. case 3:
  594. // Must be Portable Float Map
  595. if img.depth == 32 {
  596. format = .PF
  597. return
  598. }
  599. // Portable Pixel Map
  600. format = .P6 if prefer_binary else .P3
  601. return
  602. case:
  603. // Portable Arbitrary Map
  604. if img.depth == 8 || img.depth == 16 {
  605. format = .P7
  606. scale = pfm_scale
  607. return
  608. }
  609. }
  610. // We couldn't find a suitable format
  611. return {}, false
  612. }
  613. @(init, private)
  614. _register :: proc() {
  615. loader :: proc(data: []byte, options: image.Options, allocator: mem.Allocator) -> (img: ^Image, err: Error) {
  616. return load_from_bytes(data, allocator)
  617. }
  618. destroyer :: proc(img: ^Image) {
  619. _ = destroy(img)
  620. }
  621. image.register(.NetPBM, loader, destroyer)
  622. }