카테고리 없음

웹 어셈블리 이미지 처리 연습 기록 (2)

hyuckkim 2022. 4. 9. 16:38

이제 나는 웹 어셈블리의 구조도 알고 러스트도 알고 타입스크립트도 알고 png 파일 생긴 모양도 안다. 신난다.

구현해야 할 기능은 웹 어셈브리 쪽에서는 2개가 남았는데, 이미지가 팔레트 이미지인지 인식하는 기능과 팔레트 정보를 수정하는 기능이다.

팔레트 확인

이미지를 받아서 팔레트를 확인하고 팔레트가 있으면 팔레트 내용을 반환하고 팔레트가 없으면 빈 배열을 반환하는 함수를 만들었다.

#[wasm_bindgen]
pub fn read_palette(data: Uint8ClampedArray) -> Uint8ClampedArray {
    let datavec = data.to_vec();

    let mut i: usize = 8;
    if datavec[0..8] != [0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A] {
        Uint8ClampedArray::new_with_length(0)
    }
    else {
        loop {
            let length = merge_to_u32(&datavec[i..i+4]).unwrap();
            i += 4;

            if &datavec[i..i+4] == b"IDAT" {
                break Uint8ClampedArray::new_with_length(0);
            }
            if &datavec[i..i+4] == b"PLTE" {
                i += 4;
                let colors = Uint8ClampedArray::new_with_length(length);
                for j in 0..length {
                    colors.set_index(j, datavec[i + j as usize]);
                }
                break colors;
            }
            i += 4 + length as usize + 4;
        }
    }
}

png 이미지는 무조건 0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A 여덟 바이트로 시작해야 한다. 이게 아니면 png가 아니라는 뜻이다.

그 다음 바이트 부터는 길이 4바이트 - 청크이름 4바이트 - 내용 {길이}바이트 - crc 4바이트가 반복된다.

png 구조에서 IDAT 청크는 PLTE 청크보다 뒤에 와야 한다. IDAT가 먼저 나오면 팔레트가 없다는 뜻이다.

PLTE를 찾았으면 PLTE 청크 내용을 그대로 반환한다.

 

팔레트 수정

파일을 읽어 {index}번 팔레트를 {r}{g}{b}로 변경해 파일을 반환하는 함수이다.

#[wasm_bindgen]
pub fn change_palette(data: Uint8ClampedArray, index: u8, r: u8, g: u8, b: u8) 
    -> Uint8ClampedArray {
    let datavec = data.to_vec();

    let mut i: usize = 8;
    if datavec[0..8] != [0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A] {
        Uint8ClampedArray::new_with_length(0)
    }
    else {
        loop {
            let length = merge_to_u32(&datavec[i..i+4]).unwrap();
            i += 4;

            if &datavec[i..i+4] == b"IDAT" {
                break Uint8ClampedArray::new_with_length(0);
            }
            if &datavec[i..i+4] == b"PLTE" {
                let mut newvec = datavec[i..i + length as usize + 4].to_vec();
                let j = 4 + (index * 3) as usize;
                newvec[j + 0] = r;
                newvec[j + 1] = g;
                newvec[j + 2] = b;
                let result = Uint8ClampedArray::new_with_length(datavec.len() as u32);
                for ii in 0..i {
                    result.set_index(ii as u32, datavec[ii]);
                }
                for ii in 0..newvec.len() {
                    result.set_index((i + ii) as u32, newvec[ii]);
                }
                let crc = w3crc::W3Crc::make_crc_table();
                let crc = crc.crc(&newvec).to_be_bytes();
                for ii in 0..4 {
                    result.set_index((i + newvec.len() + ii) as u32, crc[ii]);
                }
                for ii in (i + newvec.len() + 4)..datavec.len() {
                    result.set_index(ii as u32, datavec[ii]);
                }
                break result;
            }
            i += 4 + length as usize + 4;
        }
    }
}

PLTE 청크를 찾을 때 까지는 기존이랑 같다.

PLTE 헤더를 찾으면 팔레트 번호의 rgb를 수정한다. 생각해보니까 팔레트 개수랑 비교하는 오류 검사가 없지만 러스트에서 (웹어셈블리에서) 오류가 날 거니까 상관 없겠군.

crc 체크를 다시 해 줘야 한다. 왜냐하면 내용이 바뀌었기 때문이다.

for ii in ...이 구문 진짜 추해 보인다.

typescript
function colornizeIfPaletted(data: ArrayBuffer, blob: Blob) {
    var colors = rust.read_palette(new Uint8ClampedArray(data));
    if (colors.length != 0) {
        pixeldata = blob;
        createNextInterface(splitColors(colors));
    }
}

이미지를검사해서 팔레트가 있으면 그걸로 이미지 ui를 고쳤다.

function createNextInterface(colors) {
    var value = {
        div: document.createElement('div'),
        button: document.createElement('input'),
        colors: new Array(),
        addColor: (self, newcover) => {
            self.div.insertAdjacentElement("beforeend", newcover);
            self.colors.push(newcover);
        },
    };
    value.div.id = 'palettemenu';
    value.button.id = 'menubutton';
    value.button.type = 'button';
    value.button.value = '다운로드';
    value.button.addEventListener("click", downloadPressed);
    bgElement.insertAdjacentElement("beforeend", value.div);
    value.div.insertAdjacentElement("beforeend", value.button);
    value.button.ariaLabel = `파일 팔레트화가 완료되었습니다. 아래에 변경할 수 있는 색 목록이 있습니다. 색 목록을 변경한 뒤 이 버튼을 눌러주세요.`;
    value.button.focus();
    value.button.addEventListener('focusout', function () {
        this.ariaLabel = `다운로드 버튼. 색 목록을 변경한 뒤 이 버튼을 눌러주세요.`;
    });
    var i = 0;
    colors.forEach(e => {
        var newcover = makeNewcover(e, i);
        value.addColor(value, newcover);
        i++;
    });
    pixelui = value;
    return value;
}

각 팔레트 색마다 input color 요소를 추가했다.

아래에 있는 거.

element 추가할 때 insertAdjacentElement보다 insertAdjacentHTML 쓰는게 가독성이 더 좋은 것 같다...

 

접근성

aria-label 속성 사용 - 접근성 | MDN (mozilla.org)

 

aria-label 속성 사용 - 접근성 | MDN

aria-label 속성은 현재 요소에 레이블을 정의하기 위해서 사용합니다. 텍스트 레이블이 화면에 표시되지 않을 때에 사용하세요. 만약에 요소에 레이블을 정의하는 화면에 보이는 텍스트가 있

developer.mozilla.org

이런 게 있어서 사용해봤다.

 

버튼마다 설명 길게 넣고, 3초정도 걸리는 파일 팔레트화 끝났을 때 설명 넣고, 맨 아래 깃허브 설명으로 안 넘어가게 설명을 넣었다.

그래서 얘가 눌러진다.

ariaLabel을 바꾸고 그거에 focus를 주면 네레이터가 읽는 느낌. 다 읽은 뒤에 또 바꾸고 싶으면 focusout에다가 ariaLabel을 또 주면 된다.

 

 


여러가지 기술과 꼼수를 익힐 수 있었던 시간이었다.

다음에는 닷넷이나 데이터베이스 쓰는 거 만들어야지.