
    	JjLM                       d Z ddlmZ ddlZddlZddlZddlZddlZddlZddl	m
Z
 ddlmZ  e
e      j                         j                  d   Zedz  Zedz  Zed	z  d
z  Zedz  Zedz  Zedz  ZdZdZ G d de      Zd(dZd)dZd*dZd+dZd,dZd-dZ d.dZ!d/dZ"d0dZ#d1dZ$d2dZ%d3d4dZ&d5dZ'd6dZ(d7dZ)d8d Z*d9d!Z+d:d"Z,d)d#Z-d;d$Z.edfd<d%Z/d=d&Z0e1d'k(  r e2 e0             y)>a-  Compare original-runtime battle effect captures against web captures.

This tool is intentionally dependency-free.  It supports non-interlaced 8-bit
PNG captures (RGB/RGBA/grayscale) and directories of PNG frames.  Without a
local capture manifest it reports the remaining blocker instead of failing.
    )annotationsN)Path)Any   outdatacapturesbattle_effect_pixel_oraclez#battle_effect_capture_manifest.jsonz'battle_effect_pixel_compare_report.jsonz'battle_effect_pixel_compare_report.html        c                      e Zd Zy)PngDecodeErrorN)__name__
__module____qualname__     ?/home/exedev/hwanse/tools/compare_battle_effect_pixel_oracle.pyr   r   "   s    r   r   c                L    t        j                  | j                  d            S )Nutf-8encoding)jsonloads	read_textpaths    r   	read_jsonr   &   s    ::dnngn677r   c                >    t        | j                  d      xs d      S )Nid )strget)rows    r   row_idr$   *   s    swwt}"##r   c                T    | sy t        |       }|j                         s	t        |z  }|S N)r   is_absoluteROOT)valuer   s     r   resolve_pathr*   .   s+    ;Dd{Kr   c                    | | j                         sg S | j                         r"| j                  j                         dk(  r| gS g S t	        d | j                         D              S )N.pngc              3  ~   K   | ]5  }|j                         s|j                  j                         d k(  s2| 7 yw)r,   N)is_filesuffixlower).0items     r   	<genexpr>zcollect_pngs.<locals>.<genexpr><   s-     f4T\\^HYHYH[_eHe$fs   ===)existsr.   r/   r0   sortediterdirr   s    r   collect_pngsr7   7   sU    |4;;=	||~**,6v>B>f4<<>fffr   c                v    t        | xs d      }dj                  d |D              j                  d      xs dS )Nr#   r    c              3  L   K   | ]  }|j                         s|d v r|nd  yw)z._-_N)isalnum)r1   chs     r   r3   zsafe_row_dir.<locals>.<genexpr>A   s#     M"u2#=Ms   "$z._)r!   joinstrip)r)   texts     r   safe_row_dirr@   ?   s7    u~D77MMMSSTXYb]bbr   c                t    	 t        | j                  t                    S # t        $ r t        |       cY S w xY wr&   )r!   relative_tor(   
ValueErrorr   s    r   display_pathrD   D   s6    4##D)** 4ys     77c                    | sy t        |       }|j                         r	 |j                  t              }n|}ddj                  |j                        z   S # t        $ r Y y w xY w)Nz..//)r   r'   rB   r(   rC   r=   parts)
path_valuer   rels      r   web_urlrJ   K   sf    
D	""4(C 388CII&&&	  		s   A 	A"!A"c                    | |z   |z
  }t        || z
        }t        ||z
        }t        ||z
        }||k  r||k  r| S ||k  r|S |S r&   )abs)abcppapbpcs          r   paethrT   Y   sY    	A	A	QUB	QUB	QUB	RxB"H	RxHr   c           
     r   | j                         }|j                  d      st        |  d      d}d x}x}x}x}}t               }|dz   t	        |      k  rt        j                  d|||dz          d   }	||dz   |dz    }
||dz   |dz   |	z    }|d|	z   z  }|
dk(  rt        j                  d	|      \  }}}}}}}n|
d
k(  r|j                  |       n|
dk(  rn|dz   t	        |      k  r||||t        |  d      |dk7  rt        |  d|       |dk7  rt        |  d|       dddd}|j                  |      }|t        |  d|       t        j                  t        |            }||z  }|dz   |z  }t	        |      |k  rt        |  d      g }d}t        |      }t        |      D ]  }||   }t        ||dz   |dz   |z          }||dz   z  }t        |      D ]  \  }}||k\  r|||z
     nd}||   }||k\  r|||z
     nd}|dk(  r|}nM|dk(  r||z   }nB|dk(  r||z   }n7|dk(  r|||z   dz  z   }n&|dk(  r|t        |||      z   }nt        |  d|       |dz  ||<    |j                  |       |} t        ||z  dz        }d} |D ]  }!t        |      D ]  }"|"|z  }#|dk(  r|!|#   }$t        |$|$|$df      || | dz    nT|dk(  r%t        |!|#   |!|#dz      |!|#dz      df      || | dz    n*t        |!|#   |!|#dz      |!|#dz      |!|#dz      f      || | dz    | dz  }   ||t        |      fS )N   PNG

z is not a PNG file   >I   r         IHDR>IIBBBBB   IDAT   IENDz is missing IHDRz uses unsupported bit depth z! uses unsupported interlace mode r      )r         z uses unsupported color type z has truncated image datar`   z uses unsupported filter    )
read_bytes
startswithr   	bytearraylenstructunpackextendr"   zlib
decompressbytesrange	enumeraterT   append)%r   r   poswidthheight	bit_depth
color_type	interlaceidatlength
chunk_typechunk_compression_filterchannels_by_typechannelsrawstrideexpectedrowsoffsetprev_yfilter_typescanir)   leftupup_leftreconrgbar   r#   xbasegrays%                                        r   decode_png_rgbar   e   sO   ??D??/0v%7899
C:>>E>F>Y>i;D
'SY
tT#cAg%67:#'#'*
S1WS1Wv-.rF{ U[UbUbcmotUuRE69j,7"KK7" 'SY
 })*;z?Qv%5677A~v%A)MNNA~v%FykRSSq)##J/Hv%B:,OPP
//%+
&CXF
f$H
3x(v%>?@@DFVDFm &kVaZ
V(;<=&1*!$ 	#HAu)*h4H%ADaB,-Md1x<(qGa!!
!$)!12!dB 88$v-F{m%TUUdlDG!	#" 	D-. UV^a'(D
C 
u 		Ax<DQ4y$)4tS*A$BSq!q$)3t9c$(mSPQ]TW*X$YSq!$)3t9c$(mSPQ]TWX\_`X`Ta*b$cSq!1HC		
 &%+%%r   c                   dd}t               }|dz  }t        |      D ]/  }|j                  d       ||z  }|j                  ||||z           1 d |dt	        j
                  d||ddddd            z    |d	t        j                  t        |      d
            z    |dd      z   }	| j                  j                  dd       | j                  |	       y )Nc                    t        j                  |       }t        j                  ||      }t        j                  dt	        |            | z   |z   t        j                  d|dz        z   S )NrX   l    )rj   crc32rg   packrf   )rx   payloadcrcs      r   ry   zencode_png_rgba.<locals>.chunk   sW    jj$jj#&{{4W.;gETXZ]`jZjHkkkr   rY   r   rV   r[   r\   rW   ra   r]   )levelr^   r   Tparentsexist_ok)rx   rl   r   rl   returnrl   )re   rm   ro   ri   rg   r   rj   compressrl   parentmkdirwrite_bytes)
r   rq   rr   r   ry   r   r   ystartr   s
             r   encode_png_rgbar      s    l
 ;DQYF6] 0AF
Duv~./0
 	
Z1aAN
O	P
uT{!<
=	> 
	  	KKdT2Wr   c                   t        t        |             }t        dt        |       d      D ]  }t        | |   ||   z
        }t        | |dz      ||dz      z
        }t        | |dz      ||dz      z
        }t        | |dz      ||dz      z
        }t	        ||||      }|r t        dt	        d|      ddf      |||dz    d}	t        |	|	|	d	f      |||dz     t        |      S )
Nr   rY   r   r`   r_   rb   (         )re   rf   rm   rL   maxrl   )
originalwebdiffr   drdgdbdadeltar   s
             r   build_diff_rgbar      s   S]#D1c(mQ' :!s1v%&!a%3q1u:-.!a%3q1u:-.!a%3q1u:-.BB#!3B3"?@D1q5M D!4tR"89D1q5M: ;r   c                   t        |       \  }}}t        |      \  }}}||f||fk7  rt        |       t        |      d||g||gddS ||z  }	d}
d}d}d}t        dt        |      d      D ]T  }d}t        d      D ]:  }t	        |||z      |||z      z
        }|s d}||z  }|||z  z  }t        ||      }< |sP|
dz  }
V |	dz  }|r||z  nd}|rt        j                  ||z        nd}|	r|
|	z  nd}d }|%|
r#t        |||t        ||             t        |      }t        |       t        |      |d||g|	|
|||||
dk(  rd	dS d
dS )NFzdimension-mismatch)r   r   dimensionsMatchoriginalSizewebSizestatusr   rY   Tr   r   passdiffers)r   r   r   r   sizetotalPixelsdifferingPixelsdifferingPixelRatiomeanAbsDeltarmsDeltamaxChannelDeltar   )
r   rD   rm   rf   rL   r   mathsqrtr   r   )r   r   diff_outputowohopixelswwwhwpixelstotal_pixelsdiffering_pixelstotal_abs_deltamax_channel_deltasum_squaredr   pixel_differschannelr   compared_channelsmean_abs_delta	rms_deltadiffering_ratio	diff_paths                          r   compare_png_pairr      s   %h/OBG%c*OBG
BxB8$X.$$HBx*
 	
 7LOK1c'lA& 
"Qx 	BGG,wq7{/CCDE $5(uu},$'(95$A!	B !
" %q(<M_'88SVN>O		+(99:UXI9E&53OI#3R_Wg-NO -	 *C R#+.&,,1&  8A r   c                   t        | j                  d            }t        | j                  d            }t        |      }t        |      }|s| j                  d      dg dS |s| j                  d      dg dS g }t        t	        |      t	        |            }t
        t        t        | j                  d      xs d            z  dz  }t        t        |d | |d |             D ]-  \  }\  }	}
	 |j                  t        |	|
|d	|d
dz               / t	        |      t	        |      k(  }|D cg c]  }|j                  d      dk7  s|t        |j                  d      xs d      t        | j                  dt                     kD  s>t        |j                  d      xs d      t        | j                  dt"                    kD  r| }}|sd}n|rd}nd}| j                  d      |t	        |      t	        |      ||t	        |      |dS # t        $ rK}|j                  t        |	      t        |
      dt        |      j                   d| d       Y d }~vd }~ww xY wc c}w )NoriginalCapturewebReferenceCapturer   zmissing-original-capture)r   r   frameComparisonszmissing-web-reference-capturer    r   diff_05dr,   zdecode-errorz: )r   r   r   errorr   r   r   r   maxDifferingPixelRatior   maxMeanAbsDeltazframe-count-mismatchr   )r   r   originalFrameCountwebFrameCountcomparedFrameCountframeCountMatchfailedFrameCountr   )r*   r"   r7   minrf   CAPTURESr@   r!   rn   zipro   r   	Exceptiontyper   float!DEFAULT_MAX_DIFFERING_PIXEL_RATIODEFAULT_MAX_MEAN_ABS_DELTA)entryoriginal_pathweb_pathoriginal_frames
web_framescomparisons
pair_countdiff_dirindexr   r   excframe_count_matchr2   failedr   s                   r   compare_entryr     sj    +<!=>MEII&;<=H"=1Oh'Jiio1Kacddiio1PfhiiKS)3z?;J,s599T?+@b'ABBVKH"+C0LjYdZdNe,f"g 	#	/#xERWX[Q\\`Ja?abc	 O,J?$88H'/05A6uyyIa  dE  @F  :G  G.).Q/%		BSUo8p2qq 	F  '	iio!/2Z(,K'	 	)  	M3x( I../r#7	   	s    /$H0BI#	I A II c                 J    t         j                         sy t        t               S r&   )MANIFESTr4   r   r   r   r   load_manifestr   ;  s    ??Xr   c                \   | sg S | j                  d      }t        |t              r3|D cg c]'  }t        |t              s|j                  d      s&|) c}S t        |t              r8|j	                         D cg c]  \  }}t        |t              sd|i| c}}S g S c c}w c c}}w )Nr	   r   )r"   
isinstancelistdictitems)manifestr	   r2   keys       r   manifest_entriesr  A  s    	||J'H(D!!)WZd-CQUWW(D!5=^^5E`	TTXZ^I_s#d#``I X`s   B# B#B#<B(B(c                    t        t        dz        } | j                  d      xs g D ch c]  }t        |       }}| j                  d      xs g D ch c]  }t        |       }}t	               }t        |      }|D cg c]  }t        |       }}|D cg c]  }|j                  d      |v s| }}|D cg c]  }|j                  d      |v s| }	}t        d |D              }
t        d |	D              }|sd}n$|
t        |      k  rd}n|t        |      k  rd	}nd
}ddd|ddg|d ut        |      t        |      t        |      |
t        |      t        |	      ||d
k(  rdndd	|dS c c}w c c}w c c}w c c}w c c}w )Nz$battle_effect_pixel_oracle_plan.jsonrecommendedFirstPassRowsfullOracleRowsr   c              3  J   K   | ]  }|j                  d       dk(  sd  ywr   r   r   Nr"   r1   r#   s     r   r3   zbuild_report.<locals>.<genexpr>U  s!     X#CGGH<MQW<WAX   ##c              3  J   K   | ]  }|j                  d       dk(  sd  ywr  r  r	  s     r   r3   zbuild_report.<locals>.<genexpr>V  s      LC0AV0KaLr
  zwaiting-for-captureszfirst-pass-incompletez!first-pass-passed-full-incompletefull-oracle-passedr   z)hwanse-battle-effect-pixel-compare-reportz+tools/compare_battle_effect_pixel_oracle.pyz(out/battle_effect_pixel_oracle_plan.jsonz(data/battle_effect_capture_manifest.jsonzeligible-for-update-goalzdo-not-call-update-goal)	manifestExistsmanifestEntriesfirstPassRowsExpectedfirstPassRowsComparedfirstPassRowsPassedfullRowsExpectedfullRowsComparedfullRowsPassedcompletionDecision)versionkindsourcer   inputssummaryr   )	r   OUTr"   r$   r   r  r   sumrf   )planr#   first_pass_idsfull_idsr   entriesr   r   first_pass_rows	full_rowsfirst_pass_passedfull_passedr   s                r   build_reportr%  L  s   SAABD-1XX6P-Q-WUWXcfSkXNX'+xx0@'A'GRHsHHHHx(G.56UM% 6D6&*Nscggdm~.MsNON $B(ABIBXXXL9LLK'	S0	0(	s8}	$4%;?66

 'd2"7|%(%8%(%9#4 #H #I)@FJ^@^"<d}

 ) # YH 7NBs)   E5E:E?F3F=F	F	c                h    | dk(  s| dk(  rdnd}d| dt        j                  t        |              dS )Nr   r  okbadz<span class='badge z'>z</span>)htmlescaper!   )r?   clss     r   badger,  w  s:    &.D,@$@$eC RCI(>'?wGGr   c                8   t        |       }|s&dt        j                  t        | xs d             dS dt        j                  |       dt        j                  |       dt        j                  |       dt        j                  t        | xs d             d	S )Nz<code>r    z</code>z	<a href='z-' target='_blank' rel='noreferrer'><img src='z' alt='z'></a><br><code>)rJ   r)  r*  r!   )rH   labelurls      r   frame_previewr0  |  s    
*
CC
(8b$9:;7CC
DKK$% &[[%&gdkk%.@-A BS)9r%:;<G	Er   c                   | j                  d      xs g }|syg }t        |d d       D ]  \  }}g }dD ]6  }||v s||   }t        |t              r|d}|j	                  | d|        8 |j	                  d|dd	t        t        |j                  d
                   dt        |j                  d      d       dt        |j                  d      d       dt        |j                  d      d       dt        j                  dj                  |      xs t        |j                  d      xs d             d        t        |      dkD  r!|j	                  dt        |      dz
   d       dj                  |      S )Nr   u3   <span class='muted'>frame comparisons 없음</span>r   )r   r   r   r   r   z.6f=z2<div class='frame-card'><div class='frame-title'>#03d r   zD</div><div class='frame-grid'><div><div class='muted'>original</div>r   z'</div><div><div class='muted'>web</div>r   z(</div><div><div class='muted'>diff</div>r   z</div></div><div class='muted'>u    · r   r    z</div></div>z<div class='muted'>... z more frames in JSON</div>)r"   rn   r   r   ro   r,  r!   r0  r)  r*  r=   rf   )r#   r   r   r   framemetricsr  r)   s           r   frame_detailsr7    s   '',-3KDE!+cr"23 
ul 	1Ce|c
eU+$SkE#aw/0	1 	)).s1U3uyy?R;S5T4U V55B599ZCXZd5e4f g00=eii>NPU0V/W X11>uyy?PRX1Y0Z [""&++fkk'.B.cc%))T[J\Jb`bFc"d!e f
	

& ;".s;/?"/D.EE_`a775>r   c                ^   | d   }g }| d   D ]  }|j                  dt        j                  t        |j	                  d                   dt        t        |j	                  d                   d|j	                  dd	       d|j	                  d
d	       d|j	                  dd	       dt        |       d        dt        j                  | d          dt        | d          d|d    d|d    d|d    d|d    d|d    dt        j                  |d          ddj                  |       dS )Nr  r   z<tr><td><code>r   z</code></td><td>r   z	</td><td>r   r   r   r   z+</td><td><details><summary>frames</summary>z</details></td></tr>a  <!doctype html>
<html lang="ko">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>Battle Effect Pixel Compare Report</title>
  <style>
    body { margin: 0; padding: 24px; font-family: system-ui, sans-serif; background: #f8fafc; color: #172033; }
    .cards { display: grid; grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); gap: 10px; margin: 16px 0 24px; }
    .card { background: white; border: 1px solid #dbe3ef; border-radius: 8px; padding: 12px; }
    .muted { color: #64748b; font-size: 12px; }
    .value { font-size: 24px; font-weight: 800; }
    table { width: 100%; border-collapse: collapse; background: white; border: 1px solid #dbe3ef; }
    th, td { border-bottom: 1px solid #e2e8f0; padding: 8px 10px; text-align: left; }
    th { background: #eaf0f8; font-size: 12px; text-transform: uppercase; }
    code { font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; }
    .badge { display: inline-block; padding: 2px 7px; border-radius: 999px; font-size: 12px; font-weight: 700; }
    .ok { background: #dcfce7; color: #166534; }
    .bad { background: #fee2e2; color: #991b1b; }
    details summary { cursor: pointer; font-weight: 700; }
    .frame-card { margin: 10px 0; padding: 10px; border: 1px solid #dbe3ef; border-radius: 8px; background: #f8fafc; }
    .frame-title { font-weight: 800; margin-bottom: 8px; }
    .frame-grid { display: grid; grid-template-columns: repeat(3, minmax(120px, 1fr)); gap: 10px; align-items: start; }
    .frame-grid img { max-width: 100%; image-rendering: pixelated; border: 1px solid #cbd5e1; background: #0f172a; }
  </style>
</head>
<body>
  <h1>Battle Effect Pixel Compare Report</h1>
  <p class="muted">source: <code>r  u   </code> · status: ze</p>
  <div class="cards">
    <div class="card"><div class="muted">manifest</div><div class="value">r  zY</div></div>
    <div class="card"><div class="muted">first pass</div><div class="value">r  rF   r  zZ</div></div>
    <div class="card"><div class="muted">full oracle</div><div class="value">r  r  zY</div></div>
    <div class="card"><div class="muted">completion</div><div class="value">r  z</div></div>
  </div>
  <table>
    <thead><tr><th>row</th><th>status</th><th>original frames</th><th>web frames</th><th>failed frames</th><th>frame details</th></tr></thead>
    <tbody>r    z$</tbody>
  </table>
</body>
</html>
)ro   r)  r*  r!   r"   r,  r7  r=   )reportr  r   r#   s       r   	html_pager:    s   YGDf~ 

S%789 :SWWX./01 277/34 577?A./ 077-q12 355B35G4H I		


"8 #'++fX.>"?!@@STYZ`aiZjTkSl mKKRScKdJe fMMTUjMkLllmnu  wN  oO  nP PNNUVfNgMhhijq  sE  kF  jG GMMQ[[Y`auYvMwLx y GGDM? K) )r   c                   |j                   j                  dd       |j                  t        j                  | dd      d       |:|j                   j                  dd       |j                  t        |       d       y y )NTr   F),:)ensure_ascii
separatorsr   r   )r   r   
write_textr   dumpsr:  )r9  json_outhtml_outs      r   write_outputsrD    st    OO$6

6*U`ghdT:If-@ r   c                 N   t        j                         } | j                  dt        t               | j                  dt               | j                         }t               }t        ||j                  |j                         t        t        j                  |d   d             y)	Nz
--json-out)r   defaultz
--html-out)r   r  F)r>  r   )argparseArgumentParseradd_argumentr   OUT_JSON
parse_argsr%  rD  rB  rC  printr   rA  )parserargsr9  s      r   mainrO    s|    $$&F
4B
40D^F&$--7	$**VI&U
;<r   __main__)r   r   r   dict[str, Any])r#   rQ  r   r!   )r)   
str | Noner   Path | None)r   rS  r   z
list[Path])r)   rR  r   r!   )r   r   r   r!   )rH   rR  r   rR  )rM   intrN   rT  rO   rT  r   rT  )r   r   r   ztuple[int, int, bytes])
r   r   rq   rT  rr   rT  r   rl   r   None)r   rl   r   rl   r   rl   r&   )r   r   r   r   r   rS  r   rQ  )r   rQ  r   rQ  )r   dict[str, Any] | None)r   rV  r   zlist[dict[str, Any]])r   rQ  )r?   r!   r   r!   )rH   rR  r.  r!   r   r!   )r9  rQ  r   r!   )r9  rQ  rB  r   rC  rS  r   rU  )r   rT  )3__doc__
__future__r   rG  r)  r   r   rg   rj   pathlibr   typingr   __file__resolver   r(   r  DATAr   r   rJ  OUT_HTMLr   r   RuntimeErrorr   r   r$   r*   r7   r@   rD   rJ   rT   r   r   r   r   r   r   r  r%  r,  r0  r7  r:  rD  rO  r   
SystemExitr   r   r   <module>ra     s>   #         H~''*
Ulf}*;;77::::$' !  	\ 	8$gc
'	H&V,$1h,^(VH
:7t <D]a A z
TV
 r   