본문 바로가기
Rust

대비 만들기

by 쪼꼬에몽 2025. 11. 18.

대비(Constrast) 필터 공식

new = (old - 128) * contrast + 128
  • 1.0은 원본
  • 1.0보다 크면 대비 증가
  • 1.0보다 작으면 대비 감소

UI에서는 보통 0~200 값을 slider로 주기 때문에 WASM에서는 다음과 같이 변환

contrastFactor = contrastValue / 100

 

contrast.rs

  • 브라우저에서 전달된 이미지 픽셀 배열을 WASM로 가져옴
  • 각 픽셀의 대비를 조절
  • 조절은 RGB 값에 ((value - 128) * factor + 128)로 수행됨
  • 변경된 값을 다시 같은 메모리에 써 넣기 때문에 JS에서 바로 반영됨 
#[no_mangle]
pub extern "C" fn contrast(ptr: *mut u8, length: usize, contrast_value: f32) {
    let pixels = unsafe { core::slice::from_raw_parts_mut(ptr, length) };
    let factor = contrast_value / 100.0;

    for i in (0..length).step_by(4) {
        let r = pixels[i] as f32;
        let g = pixels[i + 1] as f32;
        let b = pixels[i + 2] as f32;

        let nr = ((r - 128.0) * factor + 128.0).clamp(0.0, 255.0);
        let ng = ((g - 128.0) * factor + 128.0).clamp(0.0, 255.0);
        let nb = ((b - 128.0) * factor + 128.0).clamp(0.0, 255.0);

        pixels[i]     = nr as u8;
        pixels[i + 1] = ng as u8;
        pixels[i + 2] = nb as u8;
    }
}

 

#[no_mangle]

  • 함수 이름이 WASM에서 그대로 유지되도록 설정

#[wasm_bindgen]과 #[no_mangle] extern "C" 차이

  #[wasm_bindgen]  #[no_mangle] extern "C"
JS와의 연결 방식 wasm-bindgen이 자동으로 JS wrapper 생성 직접 메모리 주소로 접근하는 raw C 스타일
사용 난이도 매우 쉬움 매우 어려움 (메모리 직접 관리)
안전성 안전하고 편리 unsafe 많이 필요
데이터 전달 방식 슬라이스 & 구조체 등 Rust 타입 그대로 사용 가능 ptr + length 조합으로 직접 메모리 처리
빌드 툴 요구 wasm-bindgen / wasm-pack 필요 wasm-bindgen 없이 standalone wasm 가능
속도 약간 느릴 수 있음 (wrapper overhead) 가장 빠름 (100% raw 호출)
언제 쓰나? 일반적인 웹앱 개발 고성능 필터, 이미지 처리 같은 로우레벨 연산

#[wasm_bindgen]

  • wasm_bindgen이 자동으로 변환해 줘서 Rust 함수가 JS에서 바로 사용 가능
  • JS -> WASM, WASM -> JS 타입 변환도 자동 처리
  • &mut [u8] 같은 슬라이스도 wrapper가 알아서 JS TypedArray와 연결해줌 

#[no_mangle] extern "C"

  • 순수 C ABI (가장 로우 레벨)
  • wasm-bindgen 없이도 build 및 실행 가능
  • JS에서 메모리(buffer)를 직접 할당하고 포인터를 Rust에 전달해야 함
// JS에서 사용하는 법
const ptr = imgData.data.byteOffset;
wasmInstance.exports.contrast(ptr, imgData.data.length, 120.0);

 

편하고 쉬운 함수는 wasm_bindgen

  • brightness와 grayscale
  • 쉽게 개발 가능하고 속도도 충분 

매우 고성능이 필요한 함수는 no_mangle + raw pointer

  • contrast, convolution, sharpen, gaussian blur
  • 반복 연산이 너무 많아서 wrapper 비용까지 제거해야 함 

extern "C"

  • JS에서 호출할 수 있는 C ABI 형태

ptr

  • JS에서 넘긴 imageData.data의 포인터(메모리 주소)

length

  • 픽셀 배열 길이

contrast_value

  • 대비 값

let pixels = unsafe { core::slice::from_raw_parts_mut(ptr, length) };

  • 픽셀 배열 가져오기 
  • JS로부터 받은 메모리 주소(ptr)를 Rust 슬라이스로 변환
  • unsafe인 이유는 메모리 주소를 직접 다뤄서 Rust가 안정성 보장을 못 함
  • 이 배열은 RGBA 단위이므로 4바이트씩 하나의 픽셀

let factor = contrast_value / 100.0;

  • 대비 비율 계산
  • JS에서 constrast_value를 예를 들어 120으로 보내면 1.2가 됨

let r = pixels[i] as f32;
let g = pixels[i + 1] as f32;
let b = pixels[i + 2] as f32;

  • 현재 RGB 값을 f32로 변환

let nr = ((r - 128.0) * factor + 128.0).clamp(0.0, 255.0);

  • 대비 = 원본 값에서 128(중간값)을 기준으로 멀리 보내거나 가까이 보냄 
  • 대비를 높이면 어두운 부분은 더 어둡게, 밝은 부분은 더 밝게
  • 대비를 낮추면 전체가 플랫해짐 
  • 픽셀 값이 0 ~ 255를 넘지 않도록 제한

pixels[i]     = nr as u8;
pixels[i + 1] = ng as u8;
pixels[i + 2] = nb as u8;

  • 계산된 RGB 값을 다시 메모리에 넣기 
  • 알파 값은 유지 

하지만 본 프로젝트에서는 

[wasm_bindgen]을 이용할 것이다.

이전 밝기와 흑백에서 [wasm_bindgen]을 이용하였고,

유지 보수에 있어 [wasm_bindgen]이 더 유리하기 때문

 

export default function useFilterContrast() {
  const { prepareFilter } = useFilterBase();

  const applyContrast = (
    wasm: WasmModule | null,
    getCanvasImageData: GetCanvasImageData,
    newValue: number,
  ) => {
    const info = prepareFilter(wasm, getCanvasImageData);
    if (!info) return;

    const { ctx, imageData } = info;

    wasm?.contrast(imageData.data, newValue);
    ctx.putImageData(imageData, 0, 0);
  };

  return { applyContrast };
}
  • 대비 필터를 적용하는 함수 
export default function useFilterContrast() {
  const { prepareFilter } = useFilterBase();
  • WASM 기능 + canvas 픽셀 저장을 위한 공통 로직
  • 여러 필터에서 공유하는 base 로직
  • 필터 적용하기 전에 이미지 편집 준비를 자동으로 해주는 함수 
const info = prepareFilter(wasm, getCanvasImageData);
if (!info) return;
  • wasm 모듈이 있는지 확인
  • canvas가 있는지 확인
  • 이미지 픽셀을 읽어오기
  • 필요한 렌더링 context 가져오기 
  • 필터 적용하기 전에 이미지 편집 준비를 자동으로 해주는 함수 
const { ctx, imageData } = info;
  • ctx : canvas의 2D rendering context
  • imageData : 현재 Canvas의 RGBA 픽셀 배열
wasm?.contrast(imageData.data, newValue);
  • Rust(WASM) contrast 함수 호출
ctx.putImageData(imageData, 0, 0);
  • 변경된 픽셀을 canvas에 다시 반영 
  • WASM이 pixel 배열을 직접 수정 -> canvas에 반영 -> 즉시 UI에 표시 

'Rust' 카테고리의 다른 글

[트러블슈팅]next.js16과 tailwindcss v4 반응형 웹 오류  (3) 2025.11.22
트러블 슈팅  (1) 2025.11.21
이미지 다운로드하기  (0) 2025.11.20
밝기 조절하기  (0) 2025.11.18
파일 업로드 & 흑백 필터 적용  (0) 2025.11.14