Skip to content

PaintOp::Image 생성 단계에서 워터마크 JPEG baked image를 resolved visual payload로 일반화 #1016

@postmelee

Description

@postmelee

Refs #938
Supersedes #992
Follows #976

배경

#976은 samples/복학원서.hwp의 중앙 JPEG 워터마크를 한컴 PDF 참고 렌더에 가까운 opaque PNG로 선보정하고, SVG / WebCanvas / rhwp-studio overlay 경로에서 후단 filter, opacity, mix-blend-mode 중복 적용을 막았다.

#992는 같은 의미를 native Skia PNG export에도 적용하기 위해 열었지만, 현재 저장소가 PageLayerTree / PaintOp 기반 replay 경로와 native/browser Skia 계열 renderer로 이동 중인 점을 고려하면 renderer별 개별 패치보다 PaintOp::Image 생성 단계에서 visual payload를 공통으로 resolve하는 쪽이 더 적절하다.

현재 #976의 워터마크 JPEG bake helper는 src/renderer/svg.rs에 있고, legacy SVG / WebCanvas / Studio overlay JSON 쪽에서 각각 비슷한 판정과 후처리 생략 정책을 수행한다. 반면 native Skia와 browser CanvasKit은 PageLayerTree / PaintOp::Image를 직접 replay하므로, 같은 문서가 renderer별로 다른 이미지 payload와 후처리 정책을 탈 수 있다.

목표

  • 워터마크성 JPEG 판정과 baked PNG 변환을 renderer 공통 resolver로 일반화한다.
  • PaintOp::Image 생성 단계 또는 그 직전에서 image visual payload를 resolve한다.
  • resolved baked watermark에는 후단 ImageEffect, brightness/contrast, opacity, blend 정책이 중복 적용되지 않도록 의미를 명시한다.
  • native Skia PNG export와 browser CanvasKit direct renderer가 같은 resolved image payload를 사용한다.
  • Studio overlay JSON도 같은 resolved 결과를 사용해 renderer별 재판정을 줄인다.

구현 후보

  • #976의 watermark_jpeg_bytes_to_hancom_baked_png_bytes()renderer/svg.rs에서 공통 모듈로 이동한다.
  • ImageNode 또는 PaintOp::Image payload에 resolved image bytes / MIME / baked watermark 여부를 표현할 수 있는 구조를 추가한다.
  • LayerBuilderRenderNodeType::ImagePaintOp::Image로 낮출 때 watermark JPEG 여부를 판정하고, 성공 시 PNG bytes와 bakedWatermark 의미를 싣는다.
  • PageLayerTree JSON 직렬화는 원본 이미지 재판정 대신 resolved payload를 사용한다.
  • native Skia / browser CanvasKit renderer는 PaintOp::Image의 resolved payload만 replay하고 watermark 여부를 별도로 추론하지 않는다.
  • legacy SVG는 기존 Task #938: 복학원서 JPEG 워터마크 배경 사각형 제거 및 톤 보정 #976 동작을 유지하되, 위험이 크지 않다면 같은 resolver 호출로 연결한다. 범위가 커지면 후속 이슈로 분리한다.

비목표

  • Document IR 또는 parser 단계에서 원본 JPEG를 baked PNG로 바꾸지 않는다. baked PNG는 문서 모델이 아니라 렌더링용 visual projection이다.
  • 일반 JPEG/PNG/PCX 이미지의 렌더링 정책을 바꾸지 않는다.
  • legacy SVG 전체를 PageLayerTree renderer로 전환하지 않는다.
  • wrap=behindText / wrap=inFrontOfText paint op의 replay z-order / compositor 정책을 이 이슈에서 해결하지 않는다. 이 문제는 image payload가 아니라 PageLayerTree replay 정책 문제이며 후속 이슈로 분리한다.

완료 조건

  • samples/복학원서.hwp 1페이지의 중앙 워터마크가 PageLayerTree JSON에서 resolved PNG payload와 baked watermark 의미로 노출된다.
  • PaintOp::Image가 원본 ImageNode 의미와 별도로 resolved image bytes / MIME / baked watermark kind / 후처리 생략 의미를 표현할 수 있다.
  • native Skia PNG export와 browser CanvasKit direct renderer가 원본 JPEG를 재판정하지 않고 같은 PaintOp::Image resolved payload를 입력으로 사용한다.
  • Studio overlay JSON이 renderer별 별도 bake 판정 없이 같은 resolved 결과를 사용한다.
  • 일반 JPEG/PNG/PCX 이미지에는 워터마크 bake가 오탐 적용되지 않는다.
  • #976의 기존 SVG / WebCanvas / overlay 회귀 테스트는 유지된다.

후속으로 분리한 범위

native Skia / browser CanvasKit direct renderer에서 BehindText 워터마크가 글 내용 위에 보이는 문제는 이 이슈의 resolved payload만으로 해결되지 않는다.

이유:

  • #976의 baked watermark PNG는 투명 PNG가 아니라 opaque PNG이다.
  • 사각형 제거 효과는 background -> behindText image -> flow text 순서로 합성될 때 성립한다.
  • 현재 native Skia / browser CanvasKit direct renderer는 PageLayerTree leaf 순서를 그대로 replay하므로 wrap=behindText의 z-order 의미를 별도로 재구성하지 않는다.

따라서 PageLayerTree replay에서 BehindText / InFrontOfText z-order를 일반화하는 후속 이슈를 별도로 둔다.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions