Skip to content

Commit

Permalink
feat: moved png extraction internal
Browse files Browse the repository at this point in the history
  • Loading branch information
Vali-98 committed Sep 21, 2024
1 parent efc20d3 commit 87757ec
Show file tree
Hide file tree
Showing 4 changed files with 208 additions and 43 deletions.
24 changes: 4 additions & 20 deletions app/constants/Characters.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { db as database, rawdb } from '@db'
import { db as database } from '@db'
import { copyFileRes, writeFile } from '@dr.pogodin/react-native-fs'
import {
characterGreetings,
Expand All @@ -13,8 +13,6 @@ import { desc, eq, inArray, notInArray } from 'drizzle-orm'
import { randomUUID } from 'expo-crypto'
import * as DocumentPicker from 'expo-document-picker'
import * as FS from 'expo-file-system'
import { decode } from 'png-chunk-text'
import extractChunks from 'png-chunks-extract'
import { atob } from 'react-native-quick-base64'
import { create } from 'zustand'

Expand All @@ -23,6 +21,7 @@ import { Global } from './GlobalValues'
import { Llama } from './LlamaLocal'
import { Logger } from './Logger'
import { mmkv } from './MMKV'
import { getPngChunkText } from './PNG'
import { Tokenizer } from './Tokenizer'

type CharacterTokenCache = {
Expand Down Expand Up @@ -458,24 +457,9 @@ export namespace Characters {
return
}

const binaryString = atob(file)
const charactercard = getPngChunkText(file)

const bytes = new Uint8Array(binaryString.length).map(
(item, index) => (item = binaryString.charCodeAt(index))
)

const chunks = extractChunks(bytes)

const textChunks = chunks
.filter(function (chunk: any) {
return chunk.name === 'tEXt'
})
.map(function (chunk) {
return decode(chunk.data)
})

const charactercard = JSON.parse(atob(textChunks[0].text))
// dangerous here, card is never verified
// WARNING: dangerous here, card is never verified to fulfill v2 card spec

if (charactercard === undefined) {
Logger.log('No character was found.', true)
Expand Down
204 changes: 204 additions & 0 deletions app/constants/PNG.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,204 @@
export const getPngChunkText = (filedata: string) => {
const binaryString = atob(filedata)

const bytes = new Uint8Array(binaryString.length).map(
(item, index) => (item = binaryString.charCodeAt(index))
)
const chunk = extractChunks(bytes)
return JSON.parse(atob(decodePNG(chunk).text))
}

/// PNG DATA

function extractChunks(data: Uint8Array) {
if (data[0] !== 0x89 || data[1] !== 0x50 || data[2] !== 0x4e || data[3] !== 0x47)
throw new Error('Invalid .png file header')

if (data[4] !== 0x0d || data[5] !== 0x0a || data[6] !== 0x1a || data[7] !== 0x0a)
throw new Error(
'Invalid .png file header: possibly caused by DOS-Unix line ending conversion?'
)

let idx = 8

let firstiter = true
const uint8 = new Uint8Array(4)
const int32 = new Int32Array(uint8.buffer)
const uint32 = new Uint32Array(uint8.buffer)

while (idx < data.length) {
uint8[3] = data[idx++]
uint8[2] = data[idx++]
uint8[1] = data[idx++]
uint8[0] = data[idx++]

const length = uint32[0] + 4
const chunk = new Uint8Array(length)

chunk[0] = data[idx++]
chunk[1] = data[idx++]
chunk[2] = data[idx++]
chunk[3] = data[idx++]

// Get the name in ASCII for identification.

const name =
String.fromCharCode(chunk[0]) +
String.fromCharCode(chunk[1]) +
String.fromCharCode(chunk[2]) +
String.fromCharCode(chunk[3])
// The IHDR header MUST come first.

if (firstiter && name !== 'IHDR') {
throw new Error('IHDR header missing')
} else {
firstiter = false
}
// for cui, we only want the tEXt chunk
if (name !== 'tEXt') {
idx += length
continue
}

for (let i = 4; i < length; i++) {
chunk[i] = data[idx++]
}

// Read out the CRC value for comparison.

// It's stored as an Int32.

uint8[3] = data[idx++]
uint8[2] = data[idx++]
uint8[1] = data[idx++]
uint8[0] = data[idx++]

const crcActual = int32[0]

const crcExpect = crc32_buf(chunk)
if (crcExpect !== crcActual) {
throw new Error(
'CRC values for ' + name + ' header do not match, PNG file is likely corrupted'
)
}
// skip the rest of the chunks
return new Uint8Array(chunk.buffer.slice(4))
}

throw new Error('No tEXt chunk found!')
}

function decodePNG(data: Uint8Array) {
let naming = true

let text = ''

let name = ''

for (let i = 0; i < data.length; i++) {
const code = data[i]

if (naming) {
if (code) {
name += String.fromCharCode(code)
} else {
naming = false
}
} else {
if (code) {
text += String.fromCharCode(code)
} else {
throw new Error(
'Invalid NULL character found. 0x00 character is not permitted in tEXt content'
)
}
}
}

return {
keyword: name,

text: text,
}
}

function signed_crc_table() {
let c = 0
const table = new Array(256)

for (let n = 0; n !== 256; ++n) {
c = n
c = c & 1 ? -306674912 ^ (c >>> 1) : c >>> 1
c = c & 1 ? -306674912 ^ (c >>> 1) : c >>> 1
c = c & 1 ? -306674912 ^ (c >>> 1) : c >>> 1
c = c & 1 ? -306674912 ^ (c >>> 1) : c >>> 1
c = c & 1 ? -306674912 ^ (c >>> 1) : c >>> 1
c = c & 1 ? -306674912 ^ (c >>> 1) : c >>> 1
c = c & 1 ? -306674912 ^ (c >>> 1) : c >>> 1
c = c & 1 ? -306674912 ^ (c >>> 1) : c >>> 1
table[n] = c
}

return new Int32Array(table)
}

const T0 = signed_crc_table()

function slice_by_16_tables(T: Int32Array) {
let c = 0,
v = 0,
n = 0
const table = new Int32Array(4096)

for (n = 0; n !== 256; ++n) table[n] = T[n]

for (n = 0; n !== 256; ++n) {
v = T[n]

for (c = 256 + n; c < 4096; c += 256) {
v = (v >>> 8) ^ T[v & 0xff]
table[c] = v
}
}

const out = []

for (n = 1; n !== 16; ++n) out[n - 1] = table.subarray(n * 256, n * 256 + 256)

return out
}

const TT = slice_by_16_tables(T0)

const [T1, T2, T3, T4, T5, T6, T7, T8, T9, Ta, Tb, Tc, Td, Te, Tf, ..._] = TT

function crc32_buf(B: Uint8Array) {
let C = -1,
L = B.length - 15,
i = 0

for (; i < L; )
C =
Tf[B[i++] ^ (C & 255)] ^
Te[B[i++] ^ ((C >> 8) & 255)] ^
Td[B[i++] ^ ((C >> 16) & 255)] ^
Tc[B[i++] ^ (C >>> 24)] ^
Tb[B[i++]] ^
Ta[B[i++]] ^
T9[B[i++]] ^
T8[B[i++]] ^
T7[B[i++]] ^
T6[B[i++]] ^
T5[B[i++]] ^
T4[B[i++]] ^
T3[B[i++]] ^
T2[B[i++]] ^
T1[B[i++]] ^
T0[B[i++]]

L += 15

while (i < L) C = (C >>> 8) ^ T0[(C ^ B[i++]) & 0xff]

return ~C
}
20 changes: 0 additions & 20 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 0 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,6 @@
"expo-sqlite": "^14.0.5",
"expo-status-bar": "~1.12.1",
"expo-system-ui": "~3.0.7",
"png-chunk-text": "^1.0.0",
"png-chunks-extract": "^1.0.0",
"react": "18.2.0",
"react-autosave": "^0.4.4",
Expand All @@ -77,8 +76,6 @@
"devDependencies": {
"@babel/core": "^7.24.7",
"@types/base-64": "^1.0.2",
"@types/png-chunk-text": "^1.0.3",
"@types/png-chunks-extract": "^1.0.2",
"@types/react": "~18.2.79",
"drizzle-kit": "^0.24.2",
"eslint": "^8.56.0",
Expand Down

0 comments on commit 87757ec

Please sign in to comment.