#include #include #include #include "psych.h" /*This is where we attempt to model low-level vision processes, such as sensitivities due to the way human eyes are constructed and how data flows through the early stages of the vision system. Most of the material here is derived from Marcus Nadenau's Ph.D. thesis, though also draws on some earlier work by Scott Daly et al. \cite{Nad00, DZL+00}. Nadenau's thesis dealt almost exclusively with wavelet-based still image codecs since it was targeted at JPEG 2000, but many of the results in it can be generalized to any subband codec with a little work. This is, in fact, one of the reasons it was chosen as a starting point. It has the advantages that the algorithms described in it are fairly simple, computationally efficient, and have shown definite performance increases. Still, the results mainly apply to still image codecs. There is plenty of room for improvement by considering temporal sensitivity, temporal masking and other effects of dynamic images that we have not considered \cite{Lam96}. The techniques described in the cited reference requires at least a three frame delay, and considerable computational overhead, however. We also extend Nadenau's contrast sensitivity function measurements to the supra-threshold levels video codecs typically operate at by using the exponential model proposed by Mark Cannon Jr. \cite{Can85}. This area of the literature is in general fairly thin, but his model has the good properties of simplicity and a lack of magic constants. When adapting from Michelson contrast to our simple model, the CSF never reaches the flat curve predicted by contrast constancy in the useful range of contrast levels. However, recent research suggests that uniform quantization does not yield optimal perceptual results at contrast levels far above threshold, although the model they propose to account for this is both complicated, contains many magic constants, and is not robust to varying display parameters \cite{CH03}. @ARTICLE{Can85, author="Mark W. Cannon, Jr.", title="Perceived contrast in the fovea and periphery", journal="Journal of the Optical Society of America A", volume=2, number=10, pages="1760--1758", month="Oct.", year=1985 } @INPROCEEDINGS{CH03, author="Damon M. Chandler and Sheila S. Hemami", title="Suprathreshold image compression based on contrast allocation and global precedence", booktitle="Proc. Human Vision and Electronic Imaging 2003", address="Santa Clara, CA", month="Jan.", year=2003, URL="http://citeseer.nj.nec.com/579192.html" } @INPROCEEDINGS{DZL+00, author="Scott Daly and Wenjun Zeng and Jin Li and Shawmin Lei", title="Visual masking in wavelet compression for JPEG2000", booktitle="Proceedings of the {SPIE/IS&T} Electronic Imaging: Image and Video Communications and Processing Conference", address="San Jose", month="Jan.", year=2000, URL="http://www.ee.princeton.edu/~wzeng/EI00_wavelet_masking.zip" } @PHDTHESIS{Lam96, author="Christian J. van den Branden Lambrecht", title="Perceptual Models and Architectures for Video Coding Applications", school="\'{E}cole Polytechnique F\'{e}d\'{e}rale de Lausanne", year=1996, URL="http://ltswww.epfl.ch/pub_files/vdb/dissertation/dissertation.pdf.gz" } @PHDTHESIS{Nad00, author="Marcus Nadenau", title="Integration of Human Color Visual Models into High Quality Image Compression", school="\'{E}cole Polytechnique F\'{e}d\'{e}rale de Lausanne", year=2000, URL="http://ltswww.epfl.ch/pub_files/nadenau/Thesis_Marcus_LQ.pdf" } @ARTICLE{PSY+93, author="E. Peli and L. E. Arend and G. M. Young and R. B. Goldstein", title="Contrast sensitivity to patch stimuli: effects of spatial bandwidth and temporal presentation", journal="Spatial Vision", volume=7, number=1, pages="1--15", year=1993 } @INPROCEEDINGS{TS96, author="Trac D. Tran and Robert Safranek", title="A Locally Adaptive Perceptual Masking Threshold Model for Image Coding", booktitle="Proceedings of the {IEEE} Conference on Acoustics, Speech, and Signal Processing", address="Atlanta", volume=4, pages="1882--1885", month="May", year=1996, URL="http://citeseer.nj.nec.com/tran96locally.html" } @ARTICLE{WYS+97, author="A. B. Watson and G. Y. Yang and J. A. Solomon and J. Villasenor", title="Visibility of wavelet quantization noise", journal="{IEEE} Transactions on Image Processing", volume=6, pages="1164--1175", year=1997, URL="http://citeseer.nj.nec.com/watson97visibility.html" }*/ /*All CSF curves are scaled so that their peak is at 1.0, but JND values are computed at frequency 0, which is not the peak of the Y curve. This is 1/CSF_Y(0), which scales the frequency 0 value to 1.0 like the other curves. The question is, should this even be used? In Nadenau's earlier work, he used an arbitrary constant of 2 to scale Y channel sensitivity. In his later work, he forces the CSF curve to its maximum for every frequency below the frequency it achives that maximum at. For chroma curves, the maximum is at frequency 0, but for the luma curve, it is not. The idea is that as one moves farther away, one can increase the frequencies in the image. Thus, distortions classified as "invisible" by the CSF curve for one distance might become visible from a farther distance. Forcing the curve to its maximum for low frequencies solves this problem. So now, either one can use the modified CSF curve, which has a value of 1.0 at frequency 0, or one can scale it by the value given below, which causes its frequency 0 values to be over 20 times more important (since the filter is run in both the horizontal and vertical directions). Nadenau chose the former, with no justification, and this may likely be the better choice. His methods are at least tested and confirmed to give up to 29% bitrate reductions for visually-lossless still image compression. Other weight towards the former comes from measurements that show contrast sensitivity of unmasked 1-octave Gabor patches is low-pass, not band-pass \cite{PAY+93}, and that wavelet quantization sensitivity behaves similarly \cite{WYS+97}.*/ #define OC_YAMP (4.52514935207592383F) const float OC_YCbCr_SCALE[3][3]={ /*Unspecified.*/ { 0.62660577006129702908501F, 1.33082085846485050021660F,1.08759046924374083944404F /*JND values: 1.59589976310332425729864F, 0.75141593523980064237395F,0.91946373959616699828994F */ }, /*ITU-R BT Rec 470-6 System M.*/ { 0.58002163837392672895987F, 1.34159010104556952747146F,1.18947262510486906350593F /*JND values: 1.72407361008715120931356F, 0.74538415214948960940688F,0.84070871316759865354840F */ }, /*ITU-R BT Rec 470-6 Systems B,G.*/ { 0.67693128169295702090125F, 1.32013806299317915105007F,0.99443484770028645058915F /*JND values: 1.47725482193564916499895F, 0.75749652860752847427506F,1.00559629654228577244623F */ } }; /*The maximum number of coefficients in a CSF filter. All CSF filters are symmetric, so the real filter size is OC_CSF_FILTER_SZ_MAX*2-1.*/ #define OC_CSF_FILTER_SZ_MAX (4) /*A CSF filter. CSF filters are symmetric, so only coefficients in one direction are stored.*/ typedef float oc_csf_filter[OC_CSF_FILTER_SZ_MAX]; /*A set of CSF filters, one for each DCT coefficient. CSF filters are separable, so only filters for the coefficients in one dimension are stored.*/ typedef oc_csf_filter oc_csf_filter_bank[8]; /*Filter coefficients for the contrast sensitivity function. There is a different filter set for each color channel, with both decimated and undecimated versions for the chroma channels, and for 8 different contrast levels. These were generated with the measurements given in Chapter 5 of Nadenau's thesis and using a modified version of the method described therein. Instead of assuming an ideal frequency response with finite support, the actual frequency response of the DCT filter is used to weight the portions of the CSF that contribute to the importance of each coefficient. Sensitivity at different levels of contrast is modeled as 1/(C-T)^\alpha, where T is the threshold contrast level and \alpha is fixed at 0.5 \cite{Can85}. Thus, as C increases, the curves approach constant contrast sensitivity. See the Mathematica notebook in doc/theory/psych.nb and the code to generate the tables in tools/csfgen.c for more details. The fitlers are stored in this order: Y filters, full resolution Cr filters, half resolution Cr filters, full resolution Cb filters, half resolution Cb filters.*/ static const oc_csf_filter_bank OC_CSF_FILTERS[5][8]={ /*Y channel filters.*/ { /*Contrast level 0 filters.*/ { { +0.4974979156295645799090721F,+0.0012404707975748668555283F, +0.0000080106551093210126942F,+0.0000016274187019290664437F }, { +0.4946513130812318914664161F,-0.0027037537913987231760760F, -0.0000330471695690925902091F,-0.0000000906623724571996728F }, { +0.4938944467506038438209259F,+0.0030361290189818454336723F, +0.0000114829773190052630345F,+0.0000031200579746008261025F }, { +0.4918574148437053206173175F,-0.0040659281073912763304623F, +0.0000091342174863448405024F,+0.0000114285638008321142813F }, { +0.4849051006583633416369139F,+0.0074388198444532149800068F, +0.0000588189223675330443548F,+0.0000151937943563432782852F }, { +0.4599362228688526066378017F,-0.0096332351312863901543482F, -0.0001957936409766333567782F,-0.0000309739095527862133865F }, { +0.4171873318721850965040687F,+0.0075634147110850502918056F, +0.0001406246499985776136129F,+0.0000245145898043081554107F }, { +0.3622492868463687010205376F,-0.0079619663638459698545846F, -0.0011862532832578940970858F,-0.0004590931594731116269226F } }, /*Contrast level 1 filters.*/ { { +0.4985952211684308021943934F,+0.0006929968245834339539077F, +0.0000073638012374395989327F,+0.0000013053828352005946804F }, { +0.4969616832351805846279547F,-0.0015468288080826119845063F, -0.0000323471848981306134632F,-0.0000036473783112434650109F }, { +0.4965804229791817014927346F,+0.0016918596532595941896865F, +0.0000135821463075017275781F,+0.0000027067370702343916607F }, { +0.4954015481252519448318594F,-0.0023145025955579063420275F, -0.0000174013355874229034041F,+0.0000006393132048949258850F }, { +0.4917459840700729167828342F,+0.0040576527603305642677434F, +0.0000415233172946356360019F,+0.0000094869260538222692232F }, { +0.4782417493570770838928752F,-0.0056026658195043634036470F, -0.0001439184882724861233062F,-0.0000273935419950798459267F }, { +0.4540145745199611893028191F,+0.0048131669800966069699966F, +0.0001039464112021163945355F,+0.0000182154037458178904034F }, { +0.4193616606144400793532157F,-0.0057237897506124645052861F, -0.0008902510390405555086926F,-0.0003484643316722941109968F } }, /*Contrast level 2 filters.*/ { { +0.4993917553220076910314162F,+0.0002987792675789140432914F, +0.0000042348429586058825263F,+0.0000007166109631133404283F }, { +0.4986711203344355025635082F,-0.0006806350200641122279860F, -0.0000192753655634024158728F,-0.0000030386631529038878887F }, { +0.4985217616090412140472665F,+0.0007282459524106939079136F, +0.0000084356735629882367231F,+0.0000015356572007092566149F }, { +0.4979969696822242886113941F,-0.0010147057124000465836572F, -0.0000157298955555893672418F,-0.0000019577424496730524289F }, { +0.4964880417208658025529644F,+0.0017223189322020380272876F, +0.0000213607135587729943769F,+0.0000045470742845642022351F }, { +0.4907852358193834363397912F,-0.0024832427159493979454408F, -0.0000761508774030194012265F,-0.0000160598837680240097360F }, { +0.4802673087213260028072170F,+0.0022507695093204210280702F, +0.0000547813876165770423702F,+0.0000096506977823251094439F }, { +0.4640144269579485336585378F,-0.0029380893686057701040537F, -0.0004747406413986356718895F,-0.0001873528096329502869426F } }, /*Contrast level 4 filters.*/ { { +0.4998137274648045136338226F,+0.0000912696159462314342541F, +0.0000014843787227076340130F,+0.0000002476038959338043932F }, { +0.4995905931066523808503632F,-0.0002104358660185189956477F, -0.0000068762206074010203557F,-0.0000012099908446087239219F }, { +0.4995476579004028971908724F,+0.0002222846485656745560682F, +0.0000030368461003509543638F,+0.0000005374256197622947177F }, { +0.4993842047634874381500936F,-0.0003131139985448601423951F, -0.0000062949398654856223578F,-0.0000010179627295714944439F }, { +0.4989338104957108388504139F,+0.0005220844404367886272891F, +0.0000071708090595083798122F,+0.0000014802646157887906511F }, { +0.4972087446540453536947268F,-0.0007696923273746066876053F, -0.0000259513187718355942373F,-0.0000057346607886135720063F }, { +0.4939873062181578955431860F,+0.0007149727748709724245435F, +0.0000185889993986410929395F,+0.0000032872263385569140901F }, { +0.4888015084144361477314078F,-0.0009818722410188439417983F, -0.0001622454979429055661484F,-0.0000643148365864906962960F } }, /*Contrast level 8 filters.*/ { { +0.4999506506251316095834625F,+0.0000241593584209429973638F, +0.0000004101192692365366679F,+0.0000000681748460570138379F }, { +0.4998913134190874352213996F,-0.0000559322844779620533722F, -0.0000019105677112889524220F,-0.0000003464926297259606339F }, { +0.4998801908401898641542971F,+0.0000588248802183322815800F, +0.0000008451931104155585646F,+0.0000001485113546988487278F }, { +0.4998366341415675928594453F,-0.0000831709695403873376087F, -0.0000018013774219368567310F,-0.0000003081909154259488803F }, { +0.4997183096241975541751401F,+0.0001378632096540683619861F, +0.0000019571651197377926674F,+0.0000004004136670677446209F }, { +0.4992630422329858985719397F,-0.0002047091578843848139625F, -0.0000071181198253395804878F,-0.0000015954913789402466708F }, { +0.4984097096395556647507874F,+0.0001915690820724511792949F, +0.0000050901402230419786208F,+0.0000009014031698120338827F }, { +0.4970176720677992321384409F,-0.0002674975577999730985690F, -0.0000445377789255487871152F,-0.0000176810302770080758519F } }, /*Contrast level 16 filters.*/ { { +0.4999874755326983444270184F,+0.0000061300062681946914151F, +0.0000001052519850200102338F,+0.0000000174816076134240805F }, { +0.4999724008191679947010755F,-0.0000142077480328958145281F, -0.0000004910655416519663291F,-0.0000000897496156494382389F }, { +0.4999695954662750074781741F,+0.0000149247812929188784174F, +0.0000002173130495546358248F,+0.0000000381174217736460805F }, { +0.4999585234855704052314707F,-0.0000211232580046656607154F, -0.0000004664454746007790730F,-0.0000000809013500389663776F }, { +0.4999285617392343894493933F,+0.0000349577910372148478973F, +0.0000005007013859360666698F,+0.0000001021999933913723439F }, { +0.4998131366671812458868374F,-0.0000520077885699183199470F, -0.0000018234627141940781669F,-0.0000004102587958020365375F }, { +0.4995965797576074618469022F,+0.0000487642150325281225807F, +0.0000013033288646874188726F,+0.0000002308959439591987542F }, { +0.4992420472953436605401123F,-0.0000683984570725664186566F, -0.0000114117223943732663143F,-0.0000045321443743445158429F } }, /*Contrast level 32 filters.*/ { { +0.4999968569633094217508074F,+0.0000015382431506488678755F, +0.0000000264880574917300026F,+0.0000000043985687513150228F }, { +0.4999930729477782831438049F,-0.0000035662676828781185600F, -0.0000001236307688019724448F,-0.0000000226394465865930717F }, { +0.4999923700605397036689226F,+0.0000037451117790174111833F, +0.0000000547153501346255138F,+0.0000000095930350228033560F }, { +0.4999895903930139939852495F,-0.0000053018917810338624179F, -0.0000001176505431097798597F,-0.0000000204749807057907393F }, { +0.4999820757893215783518315F,+0.0000087707540888702817297F, +0.0000001259081133986305178F,+0.0000000256845013920447803F }, { +0.4999531171855649547275391F,-0.0000130549165552376623663F, -0.0000004586906133020776115F,-0.0000001032986356249514205F }, { +0.4998987726726807356847360F,+0.0000122467480165678056759F, +0.0000003278109523803935838F,+0.0000000580804967483879606F }, { +0.4998097224713375918980773F,-0.0000171974173459937233895F, -0.0000028707618790378860885F,-0.0000011402340485875928503F } }, /*Contrast level 64 filters.*/ { { +0.4999992134922559006149356F,+0.0000003849213285110855801F, +0.0000000066330348569149570F,+0.0000000011014154584515877F }, { +0.4999982665248277613478933F,-0.0000008924671924946150712F, -0.0000000309621248576388811F,-0.0000000056725963408933297F }, { +0.4999980907061378765376958F,+0.0000009371518088510124054F, +0.0000000137032099503720727F,+0.0000000024022681798146420F }, { +0.4999973950557881074985289F,-0.0000013267969813612056625F, -0.0000000294781131806493973F,-0.0000000051344897167296845F }, { +0.4999955148884093758532288F,+0.0000021946540386269054541F, +0.0000000315231414668302833F,+0.0000000064295873978643399F }, { +0.4999882688124631657800023F,-0.0000032670567003315287756F, -0.0000001148504838286859402F,-0.0000000258708892860719560F }, { +0.4999746697933456496265592F,+0.0000030651870298140881922F, +0.0000000820772787340354999F,+0.0000000145425660809524183F }, { +0.4999523810248664434929822F,-0.0000043055079282289645130F, -0.0000007188129863325958459F,-0.0000002855116819385006770F } } }, /*Cr channel filters at full resolution.*/ { /*Contrast level 0 filters.*/ { { +0.4466131034990036274834324F,+0.0204563871591344446920768F, +0.0023939221213980293664902F,+0.0010480976513234819148118F }, { +0.3943525077511645382521976F,-0.0194312007508983461412377F, -0.0002991392322740196491222F,-0.0000418658386670598238813F }, { +0.3602165550759829359961373F,+0.0109843676819592721283225F, +0.0000340732083994970040108F,+0.0000034293744517165677533F }, { +0.3253604730307345649364947F,-0.0073474567266921218811060F, -0.0000246081815279124990021F,+0.0000065726960631269384670F }, { +0.2935508632220520941480402F,+0.0050435024480460057330622F, +0.0000197913611940059728437F,+0.0000029381559238051280840F }, { +0.2645790141301814224483735F,-0.0037929500982503520922773F, -0.0000559497045800741237779F,-0.0000043288231670403312955F }, { +0.2388636589566937307171912F,+0.0026599487397785887346502F, +0.0000499974651329198401147F,+0.0000088670737686502255963F }, { +0.2147883058975149639380220F,-0.0028258396905114393886371F, -0.0004288976310994812375828F,-0.0001640767602695969852415F } }, /*Contrast level 1 filters.*/ { { +0.4698578102368252129572568F,+0.0119304558868460457848881F, +0.0012115903786694226604986F,+0.0005277168711196883335926F }, { +0.4386269582433506841745441F,-0.0127862923907847078619504F, -0.0001602430214692817463020F,-0.0000195722569996841122715F }, { +0.4169960531871078068810732F,+0.0081127134615084986468680F, +0.0000232588063957524358741F,+0.0000026208146395139378962F }, { +0.3929024115992683086062698F,-0.0059554576317657707124065F, -0.0000245114024840928133940F,+0.0000039573448279499121711F }, { +0.3689847372054864682588970F,+0.0044170269151178791558476F, +0.0000194609155260757375413F,+0.0000029628609638351198479F }, { +0.3453540773032837130074313F,-0.0035679797526732407765560F, -0.0000594312634054908807977F,-0.0000067749294918672538118F }, { +0.3228620127205077405818656F,+0.0026224919234070732595998F, +0.0000512831165659902757066F,+0.0000090980082325552709372F }, { +0.3002502447130425689003630F,-0.0028854418338218387057870F, -0.0004422565606365215572053F,-0.0001703816409394388322815F } }, /*Contrast level 2 filters.*/ { { +0.4867441391026595121083176F,+0.0053656903788364667534649F, +0.0004887198367536145114307F,+0.0002120418626616244243098F }, { +0.4725122075211675154449154F,-0.0062630752460282896471044F, -0.0000705061198436637121510F,-0.0000077503711692285297752F }, { +0.4623297540279411310670810F,+0.0043131917627408740065209F, +0.0000131268391170999021992F,+0.0000016229653269626313114F }, { +0.4501951498452281263951136F,-0.0034152219551933818246503F, -0.0000184809884634354849799F,+0.0000011277852315615552371F }, { +0.4373156975931161150050741F,+0.0027035532940359353892834F, +0.0000139132810869793730032F,+0.0000021444756114345932393F }, { +0.4236370538857213152716952F,-0.0023418384173699924657996F, -0.0000448056812193965004051F,-0.0000065017703255079614035F }, { +0.4097477895414575188581807F,+0.0018071018217870682756598F, +0.0000373965827200492621210F,+0.0000066363077245494702270F }, { +0.3947357774885709158496638F,-0.0020825714057240850458907F, -0.0003240988774238835897455F,-0.0001257375472981652896227F } }, /*Contrast level 4 filters.*/ { { +0.4958834656421001740866927F,+0.0016859519056663628544523F, +0.0001445025023262314664389F,+0.0000625264961989884367156F }, { +0.4913874931139418089642845F,-0.0020593861138210665766290F, -0.0000226092805204661257822F,-0.0000024066196304147228100F }, { +0.4881311311493955473750361F,+0.0014797430991360914238203F, +0.0000049264898376419930071F,+0.0000006390190043181814159F }, { +0.4840996676637792650588210F,-0.0012237890166467769222786F, -0.0000080407608920541696272F,+0.0000000413046765873424668F }, { +0.4796575272601830142171764F,+0.0010058742257652098298082F, +0.0000058176418812422918563F,+0.0000009014941215735951753F }, { +0.4747253321574411710770391F,-0.0009118419706028339311379F, -0.0000192799213040731582758F,-0.0000031517690586197395265F }, { +0.4695095348185415740083215F,+0.0007269098386637814091654F, +0.0000157577834945070641821F,+0.0000027979576676246618138F }, { +0.4635866848505370008126647F,-0.0008690766903331083705411F, -0.0001370659367807853113840F,-0.0000534285631685433790028F } }, /*Contrast level 8 filters.*/ { { +0.4989032657299813933171606F,+0.0004508885250507135020528F, +0.0000378687776724978810631F,+0.0000163682592366822710517F }, { +0.4976990343814432504743195F,-0.0005590362807171194228784F, -0.0000061344011583817215891F,-0.0000006520168673858460086F }, { +0.4968242080213455258430599F,+0.0004071780856574246858053F, +0.0000014107694341815653082F,+0.0000001857550935630192576F }, { +0.4957271332146040987431945F,-0.0003417502727829278504935F, -0.0000024117320912226457587F,-0.0000000314276727951980480F }, { +0.4945032049560656450815088F,+0.0002844597351455135101142F, +0.0000017200658041290622997F,+0.0000002669964635612131963F }, { +0.4931229281906876482644009F,-0.0002621175868537459620160F, -0.0000057553609042691053762F,-0.0000009770500636906775946F }, { +0.4916422352455450672792381F,+0.0002114292198477351887413F, +0.0000046692051973829650031F,+0.0000008293473603515466290F }, { +0.4899298619593880910194628F,-0.0002564751824144358106558F, -0.0000406737915269712537901F,-0.0000158824949035072251569F } }, /*Contrast level 16 filters.*/ { { +0.4997212087765695032182123F,+0.0001147340276179476589030F, +0.0000095831952674561008592F,+0.0000041409109405756833042F }, { +0.4994146572400975925276612F,-0.0001428285422951212522633F, -0.0000015683164162862297173F,-0.0000001668014581580321873F }, { +0.4991918010179293707651027F,+0.0001044086068593389111676F, +0.0000003660134475814273868F,+0.0000000483845855363679069F }, { +0.4989113465804591029950643F,-0.0000879850330316772930939F, -0.0000006334463649372846156F,-0.0000000113452095602269466F }, { +0.4985974113452554257186478F,+0.0000734863291078301071211F, +0.0000004499859565283966385F,+0.0000000698815740383369691F }, { +0.4982418489567451147514987F,-0.0000680230325427612738439F, -0.0000015096044607814982171F,-0.0000002588749307410131994F }, { +0.4978589080920167719845892F,+0.0000550484870529407562217F, +0.0000012222017366671539300F,+0.0000002171121142022902965F }, { +0.4974137753766228464691324F,-0.0000670551786167608278528F, -0.0000106512240094618133071F,-0.0000041611869214930319994F } }, /*Contrast level 32 filters.*/ { { +0.4999300076157416894417906F,+0.0000288122669341368914885F, +0.0000024031677817380821061F,+0.0000010383274725146474736F }, { +0.4998530180929730004635303F,-0.0000359043654502861017967F, -0.0000003943337487483114176F,-0.0000000419502539379778554F }, { +0.4997970386241146756312048F,+0.0000262705789622399434827F, +0.0000000923755404781967567F,+0.0000000122237582557993805F }, { +0.4997265277821371642907877F,-0.0000221609962758671951073F, -0.0000001603707912619345992F,-0.0000000030715237063176491F }, { +0.4996475316876837213797558F,+0.0000185253498948414021315F, +0.0000001138075497721189058F,+0.0000000176761176087779480F }, { +0.4995579622322675605872178F,-0.0000171681429803162927475F, -0.0000003820551272206407269F,-0.0000000656852223996049388F }, { +0.4994613979826325045330293F,+0.0000139052196437981325422F, +0.0000003091555877587903549F,+0.0000000549200608453141957F }, { +0.4993490028543484671175179F,-0.0000169563752736321444613F, -0.0000026945226572072131838F,-0.0000010528212293658266857F } }, /*Contrast level 64 filters.*/ { { +0.4999824833869556117704747F,+0.0000072111626411971994265F, +0.0000006012544013488351046F,+0.0000002597763464667639583F }, { +0.4999632138959408389489170F,-0.0000089885049739711238428F, -0.0000000987257403393744738F,-0.0000000105033892738312263F }, { +0.4999492023433999010606499F,+0.0000065782528205756289120F, +0.0000000231490449847509044F,+0.0000000030640136122263519F }, { +0.4999315496351057763746439F,-0.0000055506345023664364709F, -0.0000000402199501155586828F,-0.0000000007828676740402504F }, { +0.4999117683485769791928988F,+0.0000046410379760095441779F, +0.0000000285348607848787503F,+0.0000000044320502364094861F }, { +0.4998893331834736164509536F,-0.0000043022910674822050300F, -0.0000000958084528018879889F,-0.0000000164825877208729088F }, { +0.4998651398024888115045883F,+0.0000034853487944542519948F, +0.0000000775170695289236531F,+0.0000000137706518824132828F }, { +0.4998369707575275877609045F,-0.0000042512783907376547603F, -0.0000006756382634799853338F,-0.0000002639981667588252647F } } }, /*Cr channel filters at half resolution.*/ { /*Contrast level 0 filters.*/ { { +0.4695167969228932758340989F,+0.0118718351587125559715163F, +0.0012965130664463896288663F,+0.0005662371678254208033285F }, { +0.4388143301023016418227485F,-0.0120135532251189214997256F, -0.0001669247254022054969148F,-0.0000219481096099628507248F }, { +0.4181505572964309136452243F,+0.0072449968749778402565820F, +0.0000217252708311208960943F,+0.0000023403819777649205360F }, { +0.3960235893103195570219555F,-0.0051270782000218707852301F, -0.0000200642852007973282089F,+0.0000037481888840257615307F }, { +0.3748260081983196823074422F,+0.0036989182895335159101391F, +0.0000158747291663022933005F,+0.0000023973135012358275899F }, { +0.3545217626567973567830450F,-0.0029239324108313942580939F, -0.0000474467413179348193814F,-0.0000050008893046642872881F }, { +0.3356529259343632443091110F,+0.0021229471847555411896658F, +0.0000412647623712182482218F,+0.0000073203779275024357106F }, { +0.3170744801586446448560253F,-0.0023213902831152118284852F, -0.0003554375191269673302588F,-0.0001367245997123144366468F } }, /*Contrast level 1 filters.*/ { { +0.4836308306501569953539388F,+0.0064930538147626917083732F, +0.0006526985361209555226714F,+0.0002842083171947500531279F }, { +0.4666233971978723804596711F,-0.0070385576201047496402996F, -0.0000879030116116034856023F,-0.0000106718823583842997016F }, { +0.4548190941110913376732583F,+0.0045330041876995453425936F, +0.0000134778920865219048995F,+0.0000015573016337061152485F }, { +0.4415150653445629180815502F,-0.0033917098317780849069114F, -0.0000155793977831583760774F,+0.0000018471711366006795366F }, { +0.4281169344940197185600539F,+0.0025644228947526844994420F, +0.0000120451710117027005194F,+0.0000018410050994371369771F }, { +0.4146162746402144438917503F,-0.0021234871914581768606589F, -0.0000375476514876610172836F,-0.0000047810116910102886984F }, { +0.4015013744485408375695101F,+0.0015909657431346366836949F, +0.0000319351503243229408835F,+0.0000056665154596238314467F }, { +0.3879615599270475057025465F,-0.0017874386453353410721817F, -0.0002760029073079348018067F,-0.0001066589679214394040859F } }, /*Contrast level 2 filters.*/ { { +0.4931183466763838785240637F,+0.0027624669124665934827334F, +0.0002622722165700911507898F,+0.0001139622500238115544724F }, { +0.4858305119870601762599449F,-0.0031335211278896418080064F, -0.0000369976912359326878051F,-0.0000042651976814331478755F }, { +0.4806838540421569105909327F,+0.0021071386104229527895526F, +0.0000064701285123968139555F,+0.0000007849955528773996738F }, { +0.4746734787500401786530801F,-0.0016403540209556381670314F, -0.0000086974570476210647700F,+0.0000005907910888867568744F }, { +0.4684050905393940533727459F,+0.0012829464723505905852929F, +0.0000065451033860091992257F,+0.0000010068727582205162545F }, { +0.4618447215616265366655568F,-0.0011019343858519544971514F, -0.0000209715759562448650604F,-0.0000030052012807484980557F }, { +0.4552521864732587686397380F,+0.0008468924651249438995126F, +0.0000175319929402584630349F,+0.0000031116120420323132983F }, { +0.4481800437012667948089018F,-0.0009756327739151399126502F, -0.0001519317138244341113939F,-0.0000589296900896258015303F } }, /*Contrast level 4 filters.*/ { { +0.4979230383986483765568209F,+0.0008386946594426612474130F, +0.0000773258988324650365807F,+0.0000335601179077375054286F }, { +0.4957033692105178301900992F,-0.0009731912675722631609482F, -0.0000112503692463413741112F,-0.0000012688868966867814525F }, { +0.4941240320650327477736141F,+0.0006685765380286866430698F, +0.0000021155999929475169844F,+0.0000002630086767322655800F }, { +0.4922455243204088559672016F,-0.0005313457985576951377038F, -0.0000030637002743417027582F,+0.0000001278532581659137263F }, { +0.4902507554199150296980747F,+0.0004230034169241665524541F, +0.0000022676475967969978322F,+0.0000003498646273309863166F }, { +0.4881199398960199276054084F,-0.0003707873262008414298393F, -0.0000073671179573384878877F,-0.0000011179318763879160310F }, { +0.4859387204261632065005472F,+0.0002890910354338275308622F, +0.0000061009910341029670108F,+0.0000010830508017200641850F }, { +0.4835472848145313529144573F,-0.0003382010472590805429116F, -0.0000529549988910092426565F,-0.0000205825591428645541441F } }, /*Contrast level 8 filters.*/ { { +0.4994522596472190656413659F,+0.0002215954251715837803379F, +0.0000202398722059418877627F,+0.0000087807935953227835225F }, { +0.4988652324428006212819753F,-0.0002589862726493012516638F, -0.0000029777307354311767111F,-0.0000003338734239934711138F }, { +0.4984466365571178503302008F,+0.0001791227881210257541055F, +0.0000005734587681423751531F,+0.0000000718381840537506100F }, { +0.4979458094231292708542469F,-0.0001433081786963304963406F, -0.0000008500023180420679499F,+0.0000000283751801041673331F }, { +0.4974109122261932691699826F,+0.0001147401509354206179800F, +0.0000006256148297418191691F,+0.0000000966091172726374355F }, { +0.4968356983378738878087688F,-0.0001012569514721387692584F, -0.0000020414609043619688506F,-0.0000003153731734632332331F }, { +0.4962432896718967367810649F,+0.0000793254051083025387954F, +0.0000016853717541981491899F,+0.0000002992147418594681153F }, { +0.4955890275489457375002189F,-0.0000932971052573183757747F, -0.0000146365725569265970262F,-0.0000056929245737308219766F } }, /*Contrast level 16 filters.*/ { { +0.4998611570226071387779143F,+0.0000561986766606086413913F, +0.0000051201431153412290474F,+0.0000022210636548893662555F }, { +0.4997122443089725418730040F,-0.0000658075071313614969873F, -0.0000007556198837515174208F,-0.0000000845984030650239734F }, { +0.4996059977093053050545279F,+0.0000455960654235170221465F, +0.0000001464572429874675988F,+0.0000000183842398706308098F }, { +0.4994786788442057146042430F,-0.0000365446796566987810182F, -0.0000002184306321095725997F,+0.0000000068043642154947857F }, { +0.4993424888460161192860198F,+0.0000293044227604581471376F, +0.0000001605224477450457797F,+0.0000000247941451891726096F }, { +0.4991957693624383129993305F,-0.0000259080892238846858329F, -0.0000005244224383208276881F,-0.0000000814009094831105251F }, { +0.4990444159731659001799642F,+0.0000203229160262065034176F, +0.0000004325853457851024372F,+0.0000000768016102306213593F }, { +0.4988769278291567133010176F,-0.0000239374237743052331758F, -0.0000037573400835358366793F,-0.0000014617026288582778623F } }, /*Contrast level 32 filters.*/ { { +0.4999651678819846400969595F,+0.0000141005802437881223892F, +0.0000012838532484197587457F,+0.0000005569064407644136500F }, { +0.4999278024453653879177750F,-0.0000165195509739608277109F, -0.0000001896190359078884463F,-0.0000000212217826543456007F }, { +0.4999011390458626968857914F,+0.0000114511155948291469623F, +0.0000000368129637366108189F,+0.0000000046233792250619020F }, { +0.4998691745725470925165723F,-0.0000091821067151572047016F, -0.0000000549901868049509983F,+0.0000000016818023020295985F }, { +0.4998349695031858996863150F,+0.0000073658102040057640768F, +0.0000000403959275072578960F,+0.0000000062398894288430272F }, { +0.4997981029274434772169400F,-0.0000065151595659965957439F, -0.0000001320119339589072851F,-0.0000000205156585175137481F }, { +0.4997600560680905967814169F,+0.0000051123369183002634797F, +0.0000001088706632325653276F,+0.0000000193291302784124363F }, { +0.4997179319509047479286323F,-0.0000060238427959572464932F, -0.0000009456626233815313247F,-0.0000003679051068368071936F } }, /*Contrast level 64 filters.*/ { { +0.4999912843507081094962530F,+0.0000035283416991741613266F, +0.0000003212028013558364107F,+0.0000001393295230733825218F }, { +0.4999819343728570397722422F,-0.0000041341391807609398598F, -0.0000000474496259910934465F,-0.0000000053099819541588781F }, { +0.4999752621333932722791360F,+0.0000028660533055402908606F, +0.0000000092157430372188399F,+0.0000000011575647993101375F }, { +0.4999672625488200128351934F,-0.0000022984150962871844118F, -0.0000000137716468298074144F,+0.0000000004192254679993819F }, { +0.4999587013779953070802264F,+0.0000018439500011884936776F, +0.0000000101156909511318107F,+0.0000000015625767578566138F }, { +0.4999494729936705961215182F,-0.0000016311898744777719547F, -0.0000000330600760916600531F,-0.0000000051393441051615709F }, { +0.4999399481616498830405249F,+0.0000012800739524601243801F, +0.0000000272632855386633490F,+0.0000000048403902040499782F }, { +0.4999294012661086861193382F,-0.0000015084473857850950143F, -0.0000002368141965753492091F,-0.0000000921324503507289458F } } }, /*Cb channel filters at full resolution.*/ { /*Contrast level 0 filters.*/ { { +0.4312048850363129348650659F,+0.0261634755939081191378204F, +0.0031750772564368176346938F,+0.0013874848035305088669172F }, { +0.3648564476712009008174675F,-0.0238426973284627599647401F, -0.0003931252829393594699220F,-0.0000568657294143991526390F }, { +0.3223968430317957767350379F,+0.0128422442826788708175600F, +0.0000414471825883240834083F,+0.0000039752432106109812865F }, { +0.2804836413065861511206833F,-0.0082241427090125433785328F, -0.0000247997987440750155912F,+0.0000082214843948207206182F }, { +0.2435945701707277200043222F,+0.0054248167290125385436861F, +0.0000200130780606898053223F,+0.0000029201978500570578793F }, { +0.2112512721676763349787365F,-0.0039216740912408594790795F, -0.0000536838546757304984464F,-0.0000027834831585026808163F }, { +0.1835455014530386064208756F,+0.0026749983944352904649544F, +0.0000491074270152470714447F,+0.0000087068880048752066592F }, { +0.1586010497517217898710840F,-0.0027823160561639618919283F, -0.0004197741523304857730846F,-0.0001598432434828200409804F } }, /*Contrast level 1 filters.*/ { { +0.4598595927109241920938132F,+0.0159163878225080726580565F, +0.0016119432715116484636231F,+0.0006998405760071078635792F }, { +0.4181353674799145880847107F,-0.0169522280480596566898388F, -0.0002111367365725923131783F,-0.0000257194306344017703135F }, { +0.3892168484067823341909786F,+0.0106073402518933653654321F, +0.0000291961906781608349808F,+0.0000032024654446753188805F }, { +0.3573460821204919035665171F,-0.0076310457238191273937611F, -0.0000277582398580081882698F,+0.0000059669593279700587160F }, { +0.3261672078827498877373614F,+0.0055400531916913013652515F, +0.0000227672648134058980317F,+0.0000034510563457196915127F }, { +0.2959995531918351852063154F,-0.0043536784504266436146569F, -0.0000678837955147546022548F,-0.0000066564449870218959590F }, { +0.2679167049092647800989653F,+0.0031318494348818205763596F, +0.0000595772196036073009139F,+0.0000105689747934208020362F }, { +0.2404884151403481606923407F,-0.0033699165768711993908247F, -0.0005126013690148204437688F,-0.0001967977370010746637242F } }, /*Contrast level 2 filters.*/ { { +0.4817120792465502399082311F,+0.0074724315808237231836819F, +0.0006516392309702905005775F,+0.0002815245153035708203064F }, { +0.4617799787784527820733160F,-0.0089330693685947138577008F, -0.0000955207550748095015258F,-0.0000099040179076770895627F }, { +0.4473378472950032547217347F,+0.0062551941429642701417158F, +0.0000184705180670257794635F,+0.0000023023823007004419637F }, { +0.4298781400943726405117218F,-0.0049940173805803457843244F, -0.0000262842187295421361552F,+0.0000018287017578816391974F }, { +0.4111460548094960087972538F,+0.0039688514372345591249558F, +0.0000200138575890065358422F,+0.0000030864724760458936325F }, { +0.3911410298434226384678425F,-0.0034301165480603622651157F, -0.0000642853214180271072655F,-0.0000091227921858019962628F }, { +0.3708010298878299249025758F,+0.0026360598441461957110177F, +0.0000538723161638011622024F,+0.0000095574740153336816919F }, { +0.3489120970486987993730565F,-0.0030100382871891414281185F, -0.0004664723745621126649655F,-0.0001807859522167991827516F } }, /*Contrast level 4 filters.*/ { { +0.4941658856998470961130465F,+0.0024233123979785255175390F, +0.0001932074875662472858429F,+0.0000831105211319156379464F }, { +0.4876602722868220163299213F,-0.0030905075007501259763687F, -0.0000325596218445339458309F,-0.0000032790980791743816316F }, { +0.4828792576859727847526926F,+0.0022985545858219516741905F, +0.0000079624515509959584961F,+0.0000010617060879139090464F }, { +0.4767652919061929894795071F,-0.0019577346550704111463603F, -0.0000139992621280691532519F,-0.0000002220327960929667512F }, { +0.4698333763048777589332872F,+0.0016461203637354723458563F, +0.0000099940339496473717793F,+0.0000015523470412585898549F }, { +0.4619130244036616095293368F,-0.0015265210571683931507769F, -0.0000334995202072480791826F,-0.0000056997504999518381849F }, { +0.4533427707255207050884849F,+0.0012344541050524223367579F, +0.0000271744585322142675251F,+0.0000048252310570862587505F }, { +0.4433828521055653837379396F,-0.0014946768504321328221301F, -0.0002366335603871463639635F,-0.0000923892560281006428036F } }, /*Contrast level 8 filters.*/ { { +0.4984288343975059598633948F,+0.0006562052364283222152808F, +0.0000507157536390280400920F,+0.0000217706132202295523607F }, { +0.4966642306360010139520966F,-0.0008555366892458960911827F, -0.0000091624242974067453081F,-0.0000009415980610145982637F }, { +0.4953645681975468728630574F,+0.0006488105721603400384556F, +0.0000024319488330673602594F,+0.0000003310769067458522383F }, { +0.4936691099865914145183865F,-0.0005654495738762885985798F, -0.0000045674889466461169709F,-0.0000002004098700369449674F }, { +0.4917103047825460149233834F,+0.0004848166883451819619832F, +0.0000031824191720537174520F,+0.0000004955723362595859390F }, { +0.4894149820816336426965165F,-0.0004620263922605706346722F, -0.0000108293930905426763809F,-0.0000019525940715985574606F }, { +0.4868725721430893771390913F,+0.0003810932905231036670578F, +0.0000086774531525387136184F,+0.0000015419547591789487515F }, { +0.4838244145301845988704770F,-0.0004736707507444618599907F, -0.0000757640541296034704822F,-0.0000296696124696210131037F } }, /*Contrast level 16 filters.*/ { { +0.4995993603725594200959392F,+0.0001675790051963064412194F, +0.0000128413222402595711072F,+0.0000055087749513106696301F }, { +0.4991485323900807236086052F,-0.0002198113706306171393021F, -0.0000023704700214431176567F,-0.0000002458308213386123646F }, { +0.4988163838127456783944069F,+0.0001675683041352075974568F, +0.0000006433155295694552215F,+0.0000000880597963312900939F }, { +0.4983806733625466556247829F,-0.0001469739614975548638170F, -0.0000012295239077028707444F,-0.0000000632154280827319493F }, { +0.4978746999205063694660112F,+0.0001266897221764517295853F, +0.0000008508501271006457350F,+0.0000001325943198630668791F }, { +0.4972775592456645243899516F,-0.0001216788456608324940807F, -0.0000029075619999733626398F,-0.0000005324822709820448539F }, { +0.4966117614934890123201683F,+0.0001009310800574111260421F, +0.0000023216901090831847279F,+0.0000004126671295303846784F }, { +0.4958062635952286134610745F,-0.0001264380539211572914045F, -0.0000202877312685135985186F,-0.0000079517431916540738722F } }, /*Contrast level 32 filters.*/ { { +0.4998993351771336968170090F,+0.0000421220032475569009607F, +0.0000032206817961775215503F,+0.0000013813954564573950940F }, { +0.4997860047929443272352046F,-0.0000553368227772964353618F, -0.0000005979284126332098208F,-0.0000000621689973188208138F }, { +0.4997025033015418116200124F,+0.0000422407185527253454887F, +0.0000001631914270623181817F,+0.0000000223693885536100517F }, { +0.4995928101081758554435908F,-0.0000371101499479539579957F, -0.0000003132820523681314136F,-0.0000000167087493931284721F }, { +0.4994652608212478139826374F,+0.0000320321692643728164838F, +0.0000002164152696408874873F,+0.0000000337321614787282274F }, { +0.4993144520397829189128913F,-0.0000308273860686345823625F, -0.0000007403465875189430361F,-0.0000001361243203404831883F }, { +0.4991460174323619214398207F,+0.0000256081933915283632122F, +0.0000005906349708546492738F,+0.0000001049897350000978661F }, { +0.4989417607647111396751427F,-0.0000321459342115372963917F, -0.0000051623025155033980537F,-0.0000020238177523863364102F } }, /*Contrast level 64 filters.*/ { { +0.4999748020039830875838049F,+0.0000105448136884012114769F, +0.0000008058206653170043856F,+0.0000003456125767547167418F }, { +0.4999464301453608316982979F,-0.0000138584245076405129918F, -0.0000001498193746973520146F,-0.0000000155877105180651935F }, { +0.4999255255535027808733162F,+0.0000105821860375712442561F, +0.0000000409480959457036872F,+0.0000000056148971136907884F }, { +0.4998980539981414117356451F,-0.0000093007142669651292756F, -0.0000000786964156299724979F,-0.0000000042351883624420844F }, { +0.4998661000495203121651855F,+0.0000080308026967991907055F, +0.0000000543394130767120918F,+0.0000000084701788489652804F }, { +0.4998283015377052440619821F,-0.0000077326874018139165278F, -0.0000001859434346391019483F,-0.0000000342227331225284434F }, { +0.4997860672041459784153972F,+0.0000064258732348099073471F, +0.0000001483085835788009634F,+0.0000000263634466317491599F }, { +0.4997348201514046905913347F,-0.0000080705958338840041262F, -0.0000012963274322268743507F,-0.0000005082386609530270635F } } }, /*Cb channel filters at half resolution.*/ { /*Contrast level 0 filters.*/ { { +0.4602086625268231867558200F,+0.0154644101646652411263805F, +0.0017139680661844658524778F,+0.0007466224261652644900861F }, { +0.4203147358444114800590796F,-0.0153576587604778470874667F, -0.0002193317653509145798759F,-0.0000293024896882381180032F }, { +0.3936824926826040793592654F,+0.0090546142646650024793376F, +0.0000271981448294032643288F,+0.0000028520968546003849985F }, { +0.3656388142886505265849451F,-0.0062728057224575783576426F, -0.0000229109237204130183061F,+0.0000050338770173233405873F }, { +0.3392500264538516607792928F,+0.0044399735298648775050356F, +0.0000183308323441815770936F,+0.0000027524756651677612665F }, { +0.3144590386416039051731275F,-0.0034410259141278900368865F, -0.0000537025101106421577212F,-0.0000050849766445565794187F }, { +0.2918304760154632471191860F,+0.0024638672926468457857752F, +0.0000472080766446111824868F,+0.0000083739749129400848558F }, { +0.2699936349490731912759145F,-0.0026616819969186894696833F, -0.0004059940385696034735596F,-0.0001558329947118572704652F } }, /*Contrast level 1 filters.*/ { { +0.4781615085884826643614076F,+0.0086926110131892875970694F, +0.0008643706989259012513083F,+0.0003751101743641625449466F }, { +0.4553496839535168261292597F,-0.0094487315134822169593320F, -0.0001166235533015845874088F,-0.0000140081971928498450576F }, { +0.4394739520269050903067409F,+0.0060756761788154593079136F, +0.0000177467260284247707505F,+0.0000020379596196937894010F }, { +0.4216018120739419283715677F,-0.0045252927687756908797789F, -0.0000200512127055128185288F,+0.0000026443219136367930252F }, { +0.4036527395526329220487582F,+0.0034038308211054510432625F, +0.0000156547693173164447324F,+0.0000023906039343116392064F }, { +0.3856583774696092370426470F,-0.0027975648503294914953843F, -0.0000485187662361873521294F,-0.0000059794524511424401126F }, { +0.3682790881502632229782535F,+0.0020835009412891406649360F, +0.0000414545852249780631010F,+0.0000073551312341149776368F }, { +0.3504823381563014206285800F,-0.0023245622127997115149989F, -0.0003580271056274965694663F,-0.0001382211213773269482905F } }, /*Contrast level 2 filters.*/ { { +0.4906477859441781275151584F,+0.0037824671969380191383081F, +0.0003478083577686764070978F,+0.0001505152822959150151708F }, { +0.4806278681534560948129808F,-0.0043717135806460331523549F, -0.0000500855122094850846079F,-0.0000055960073257212778543F }, { +0.4734921247384216158593517F,+0.0029813011626340811185965F, +0.0000091368455226524458512F,+0.0000011218339131127151912F }, { +0.4650586901118646254715827F,-0.0023448450933404494343770F, -0.0000126910334944193751934F,+0.0000007747036541429312930F }, { +0.4561722239177513626984251F,+0.0018480827148408185064965F, +0.0000095324075788810215797F,+0.0000014683983753382557532F }, { +0.4467855777070405820516896F,-0.0015977740464431693448444F, -0.0000306793872423118341381F,-0.0000044606182934143019040F }, { +0.4372856389699626977751734F,+0.0012327172165589896351418F, +0.0000255927010398500871867F,+0.0000045421711509735831212F }, { +0.4270287690541813563349649F,-0.0014237534941442383726318F, -0.0002218497230922978811184F,-0.0000860857157147907237245F } }, /*Contrast level 4 filters.*/ { { +0.4971460649591666336455376F,+0.0011636399729905537447916F, +0.0001026463529986448337686F,+0.0000443439754802337109184F }, { +0.4940510732579490027482905F,-0.0013879215457904354926594F, -0.0000155356132537631831116F,-0.0000016900389065118308948F }, { +0.4918260770604944154271720F,+0.0009749674164589081353655F, +0.0000031493379731592939307F,+0.0000003999561078445047152F }, { +0.4891271714038635254340193F,-0.0007898209927561170018429F, -0.0000048491536852577602865F,+0.0000001135143101289031343F }, { +0.4862096615978594993379147F,+0.0006385111597891724799850F, +0.0000035506447732053923992F,+0.0000005490728968264281322F }, { +0.4830350565738341095389785F,-0.0005688295077240233853580F, -0.0000116529508005388360943F,-0.0000018369478686018179049F }, { +0.4797342094944266333023108F,+0.0004483154263745252787916F, +0.0000095871420639472317411F,+0.0000017021279368523581238F }, { +0.4760531716997873918906237F,-0.0005301129386037866080689F, -0.0000833041296515965882987F,-0.0000324253810482480852423F } }, /*Contrast level 8 filters.*/ { { +0.4992444835974491068597558F,+0.0003088435876279362166281F, +0.0000268779569733892417891F,+0.0000116042498986108184937F }, { +0.4984220668736984438673687F,-0.0003721105376605886175603F, -0.0000041467601158370050266F,-0.0000004486458888485877789F }, { +0.4978292885293383318590088F,+0.0002638585555855297920189F, +0.0000008707883622934384889F,+0.0000001117623868345653658F }, { +0.4971041132935203887832643F,-0.0002158359416585387110989F, -0.0000013847094803124767368F,+0.0000000156756949140729970F }, { +0.4963136741525427386712011F,+0.0001759437344031844984403F, +0.0000010049726651719525168F,+0.0000001555955914948288237F }, { +0.4954449600589856950172418F,-0.0001583577899523404405521F, -0.0000033192061665273614916F,-0.0000005366863223510101579F }, { +0.4945334300806312199938475F,+0.0001257251918016510656550F, +0.0000027180227895753001599F,+0.0000004826469863732972892F }, { +0.4935054054165609049853458F,-0.0001499437205014660632902F, -0.0000236378241409296686122F,-0.0000092107328841456947798F } }, /*Contrast level 16 filters.*/ { { +0.4998082891290608342771407F,+0.0000784225821887767093367F, +0.0000068001654637878569993F,+0.0000029353831595763431232F }, { +0.4995993942162609280366325F,-0.0000947438742531420726844F, -0.0000010548637801785191399F,-0.0000001139981807388965032F }, { +0.4994487283513495134990023F,+0.0000673499430049362179983F, +0.0000002236391116305261782F,+0.0000000287839074709941245F }, { +0.4992639881352125552638199F,-0.0000552367080491171106920F, -0.0000003586844583628537256F,+0.0000000028994376696220573F }, { +0.4990621724709565221189678F,+0.0000451284840720293943983F, +0.0000002596895744578047987F,+0.0000000402194703252865477F }, { +0.4988397692343520262170387F,-0.0000407318132483162168868F, -0.0000008591626814938898862F,-0.0000001398615903289499636F }, { +0.4986058246389627468531103F,+0.0000324031698429677145158F, +0.0000007026513732516271905F,+0.0000001247781641005778767F }, { +0.4983411637385160020130570F,-0.0000387374947664184600838F, -0.0000061122319350186889525F,-0.0000023824033322084650407F } }, /*Contrast level 32 filters.*/ { { +0.4999518918227697228395812F,+0.0000196829210259502925928F, +0.0000017051612382712894011F,+0.0000007360229095357550227F }, { +0.4998994581886274657378522F,-0.0000237957337813770248615F, -0.0000002648825975177812061F,-0.0000000286180329343052322F }, { +0.4998616340735522745042374F,+0.0000169262785037985382926F, +0.0000000562941480146951632F,+0.0000000072506125955771948F }, { +0.4998152285910394954093761F,-0.0000138912800753890178001F, -0.0000000904840948884158553F,+0.0000000006569053684572551F }, { +0.4997645050846114544995658F,+0.0000113556814985429743562F, +0.0000000654703268447273447F,+0.0000000101405530586313040F }, { +0.4997085684350353051996763F,-0.0000102567015065452848152F, -0.0000002166976700434084466F,-0.0000000353365038058256193F }, { +0.4996496916819095912565274F,+0.0000081636499530921329481F, +0.0000001771646242092627697F,+0.0000000314616523949115513F }, { +0.4995830317103385764809786F,-0.0000097655218729216240113F, -0.0000015412181378217517645F,-0.0000006007760062033873553F } }, /*Contrast level 64 filters.*/ { { +0.4999879616215221478370268F,+0.0000049255846539969078327F, +0.0000004266115348804893581F,+0.0000001841423067367882810F }, { +0.4999748400262142289385281F,-0.0000059558338066581850766F, -0.0000000662940618596535339F,-0.0000000071619736813179048F }, { +0.4999653740934134060758254F,+0.0000042371546362524793879F, +0.0000000140977703995967159F,+0.0000000018160990882293691F }, { +0.4999537588655347808419549F,-0.0000034779875496128436171F, -0.0000000226723427446380982F,+0.0000000001599095928602632F }, { +0.4999410610317421288506523F,+0.0000028435525305817017172F, +0.0000000164021520985583606F,+0.0000000025405447297687155F }, { +0.4999270557298786110678179F,-0.0000025688225646702732445F, -0.0000000542947722626940048F,-0.0000000088575719125639121F }, { +0.4999123119399688786046454F,+0.0000020448752725206912492F, +0.0000000443859118778854798F,+0.0000000078822671904045956F }, { +0.4998956157616588069636521F,-0.0000024464996226189751516F, -0.0000003861349358219454581F,-0.0000001505205748074876969F } } } }; /*Distortion: We measure distortion using Nadenau's IaCLA-2 masking model, as it performed the best in his model comparisons. Nadenau only used the model to predict the detection threshold for distortions, and never gave an example of the bitrate savings achievable by incorporating it into a visually-lossless compression system, as he did with his CSF filtering and texture synthesis. Thus, it is unknown on a quantifiable level how much this actually contributes to performance. Daly mentions that "the various visual optimization tools" when combined have produced observed bit rate savings as high as 50%, though it is unclear which tools he is referring to, and no reference is given. Most likely he is talking about CSF filtering, point-wise masking and neighborhood masking. Unlike CSF filtering, which provides little benefit at low resolutions and high contrast, neighborhood masking can provide bit rate reductions at any resolution or contrast. The IaCLA-2 masking model: D = sum_{block coefficients i,j} (|c_{ij}'-\hat{c}_{ij}|/ (1+(1/(k_L^\nu N_{neighbors})) sum_{neighbors k.l} |c_{kl}'|^\nu)^ \epsilon)^\Beta)^{1/\Beta} c_{ij}' = r_{csf} w_{csf} c_{ij} \hat{c}_{ij} = r_{csf} w_{csf} \lfloor c_{ij}/(\Delta Q) \rfloor \Delta Q Parameters: r_{csf} = 1/128 (Nadenau used 1/8) \epsilon = 1 (Nadenau claimed he used 0, but probably meant 1) \Beta = \infty (Nadenau used 20) N_\Gamma = 79-99 (Nadenau used 84) k_L = 1E-4 (JPEG 2000 used 1E-4, Nadenau used 3E-6) \nu = 0.2 (Nadenau estimated 0.389 and 0.477). A detailed discussion of each parameter is given below. r_{csf}: Coefficient scale factor. Nadenau's explanation of r_{csf} was very brief: Additionally, the viewing coniditions for the psycho-visual test were chosen, so that the spatial frequencies affected by quantization correspond to the frequencies for which the human observer is most sensitive.... However, this way it is only guaranteed that the relative CSF-sensitivity equals 1, but it needs to be expressed in terms of the dynamic range of the wavelet coefficients c_{ij}. This is done by setting c_{ij}' to: c_{ij}' = r_{csf} c_{ij}, where r_{csf} is approximately 1/8. No description of how the value 1/8 was derived is present. However, the FCD of the JPEG 2000 standard gives us a hint of what a proper value might be. In Part II, Annex E, Section 2, it discusses the "point-wise extended non-linearity" on which Nadenau's IaCLA-2 method is based. There it suggests using a parameter a which would cause k_L to have an equivalent value of 2^{component_bit_depth-1}*1E-4. Nadenau reports, however, that the standard specifies a value of 1E-4 directly. This makes us assume he has separated out the 2^{component_bit_depth-1} part into his r_{csf} parameter. Thus, we adopt a value of 1/(2^7) for r_{csf}. The remaining discrepancy with Nadenau's value of 1/8 is likely accounted for by some scaling introduced by his wavelet transform. The scale factor of 2 in our DCT transform is already accounted for in our CSF filter computations. \epsilon: Threshold elevation non-linearity.. The original IaCLA model (used in JPEG 2000) also contained an exponent, \epsilon, on the neighborhood threshold elevation term. Nadenau found that this actually hurts performance, and in his IaCLA-2 model claims to set it to 0. This was probably meant to be 1, since 0 would've disabled neighborhood masking altogether. \Beta: Error pooling exponent. Again for simplicity, we use \Beta=\infty to turn the pooling into a max operator, which is a close approximation for the fixed value of \Beta=20 used by Nadenau. N_\Gamma: Masking neighborhood size. Daly et al. use a causal masking neighborhood in a wavelet transform \cite{DZL+00}. They take all causal coefficients--those above or to the left--from the same band in a 13x13 neighborhood, i.e. 84 coefficients. They also talk about encroachments of the horizontal and vertical bands in a wavelet decomposition on the diagonal bands, which can lead to excessive horizontal and vertical masking around diagonal edges and decreased diagonal masking. The DCT has smaller frequency bands than wavelets, and so we can get a slight improvement by moving some coefficients around in the high frequency channels. Each coefficient uses a combination of nearby coefficients in the same DCT block and nearby coefficients in neighboring blocks for neighborhood masking. The number of coefficients in the same block is chosen to approximate the octave structure of the HVS, while the number of neighboring blocks used is chosen to give nearly the same number of neighbors for each masking group. There are 9 groups for a single 8x8 DCT block: 0 1 2 3 4 5 6 7 +-+-+---+-------+ 0 | |A| | | +-+-+ D | | 1 |B|C| | | +-+-+---+ G | 2 | | | | | E | F +-+ | 3 | | | | | +---+-+-+ +---+ | 4 | | | | | +-+ +-+ 5 | | | | H | I | 6 | | | | +-+ | 7 | | | +---------+-----+ (no neighborhood masking is applied to the DC coefficient) Groups A through C use a 9x9 neighborhood of blocks, for a total of 1*9*9-1=80 neighbors. Groups D through F use a 5x5 neighborhood of blocks, for a total of 4*5*5-1=99 neighbors. Groups G through I use the top, bottom, left and right neighboring blocks, for a total of 16*5-1=79 neighbors. There might be a smarter way to group these coefficients. In addition, there is no reason that the groupings must form a partition, but instead each coefficient could belong to multiple groups, potentially increasing the total number of groups to one per coefficient. One could also conceivably weight neighbors in a non-uniform manner. This approach was investigated by Tran and Safranek \cite{TS96}, but too little detail was reported to reproduce their results. Tran's Master's thesis might contain more information, but no copy is available online. k_L: Masking dynamic range This parameter controls the degree to which a signal can be masked. We use the JPEG 2000 value. See below for discussion. \nu: Neighborhood coefficient exponent Nadenau's estimates for \nu on observer data where observers have unlimited time and can flicker back and forth between a distorted and undistorted image were 0.389, with a standard deviation of 0.0142. He found that values in the range of 0.4 were less likely to declare homogenous regions near strong edges active than more usual values in the literature, such as 2, while still classifying medium-sized coefficients as active. However, for observer data where observers had a limited amount of time and could not flicker back and forth between the two still images, the estimates grew to 0.477, with a standard deviation of 0.0171. Unforunately, attempts to reproduce Nadenau's experiments have yielded nonsense results. With the small value of k_L and large value of \nu he suggests, coefficients are masked by a value nearly two orders of magnitude greater than with the JPEG 2000 recommended parameters. Clearly some part of his description has been misinterpreted or was not sufficiently described, but in any case the results are completely unusable as-is. Adopting the JPEG 2000 parameter values seems to give plausible results, however, and so until this discrepancy can be resolved, that is what we have done. Finally, Nadenau ran the computed distortion through a Weibull fuction to compute the probability of detection. This function was parameterized by a base distortion, D_0, and a slope value, \kappa, which he estimated from observer data. The Weibull function is monotonic, and outside a narrow band around D_0 it is very close to either 0 or 1, so we forgo applying it at all. A note about point-wise masking: Point-wise masking or self-masking or luma masking is sometimes suggested to account for the HVS's decreased sensitivity in bright regions and vice versa. This type of masking is actually built in to our compression system since we operate on gamma-pre-corrected signals with uniform quantization. In effect, we are really performing non-uniform quantization on the underlying linear signals. The gamma exponent is not equal to the value of 3.0 generally assumed for the HVS, but it is close enough that additional effort on our part would not likely bring much improvement.*/ /*This parameter, r_{csf}^\nu, compensates for the dyanmic range of the DCT coefficients.*/ #define OC_MASK_R_CSF_NU (8.8388347648318440550E-2F) /*#define OC_MASK_R_CSF_NU (0.37892914162759952059F)*/ /*This parameter, k_L^\nu, determines the dynamic range of the neighborhood masking.*/ /*#define OC_MASK_K_L_NU (1.7320508075688772935E-3F)*/ #define OC_MASK_K_L_NU (0.15848931924611134852F) /*#define OC_MASK_NU (0.5F)*/ #define OC_MASK_NU (0.2F) /*The maximum half-width in blocks of a masking neighborhood. This half-width is rounded up for consistency with the OC_CSF_FILTER_SZ_MAX constant, so the real neighborhood width and height are OC_MASK_WINDOW_SZ_MAX*2-1.*/ #define OC_MASK_WINDOW_SZ_MAX (5) /*The number of DCT coefficient groups used for masking.*/ #define OC_MASK_NGROUPS (9) /*The number of DCT coefficient groups that use a full NxN neighborhood.*/ #define OC_MASK_NFULL_GROUPS (6) /*The half-width in blocks of each masking neighborhood, rounded down for consistency with csf_filter_sizes values.*/ static const int OC_MASK_WINDOW_SIZES[OC_MASK_NGROUPS]={ 4,4,4,2,2,2,1,1,1 }; /*The masking group that each DCT coefficient belongs to.*/ static const int OC_MASK_GROUP[64]={ 9,0,3,3,6,6,6,6, 1,2,3,3,6,6,6,6, 4,4,5,5,6,6,6,6, 4,4,5,5,8,6,6,6, 7,7,7,8,8,8,8,6, 7,7,7,7,8,8,8,8, 7,7,7,7,8,8,8,8, 7,7,7,7,7,8,8,8 }; /*The weights for each coefficient masking group. These terms absorb a good number of the constants in the model.*/ static const float OC_MASK_WEIGHTS[OC_MASK_NGROUPS]={ OC_MASK_R_CSF_NU/(OC_MASK_K_L_NU*80), OC_MASK_R_CSF_NU/(OC_MASK_K_L_NU*80), OC_MASK_R_CSF_NU/(OC_MASK_K_L_NU*80), OC_MASK_R_CSF_NU/(OC_MASK_K_L_NU*99), OC_MASK_R_CSF_NU/(OC_MASK_K_L_NU*99), OC_MASK_R_CSF_NU/(OC_MASK_K_L_NU*99), OC_MASK_R_CSF_NU/(OC_MASK_K_L_NU*79), OC_MASK_R_CSF_NU/(OC_MASK_K_L_NU*79), OC_MASK_R_CSF_NU/(OC_MASK_K_L_NU*79), }; /*This constant is used to absorb noise in the CSF values of low-amplitude coefficients.*/ #define OC_CSF_NOISE_LEVEL (8) /*The amount that the DC coefficient of the DC transform is shifted by.*/ #define OC_CSF_DC_SHIFT (4096) typedef float oc_weight_block[64]; typedef float oc_mask_block[9]; /*Scratch space used by the psychovisual model.*/ struct oc_psych_ctx{ /*The CSF filter pipeline stage.*/ oc_enc_pipe_stage csf_pipe; /*The spatial masking pipeline stage.*/ oc_enc_pipe_stage mask_pipe; /*A single row of CSF-filtered coefficients (after vertical filtering). This has OC_CSF_FILTER_SZ_MAX-1 blocks of padding on each side.*/ oc_weight_block *csf_row; /*OC_MASK_WINDOW_SZ_MAX rows of CSF-filtered coefficients (after both vertical and horizontal filtering) for each plane. This has no padding on either side.*/ oc_weight_block **csf_weights[3]; /*The half-width of the CSF filters in the current filter banks, rounded down.*/ int csf_filter_sizes[5][8]; /*The current CSF filter banks. There are 5 in total: 0: Y channel (horizontal and vertical). 1: Cr channel horizontal. 2: Cr channel vertical. 3: Cb channel horizontal. 4: Cb channel vertical.*/ oc_csf_filter_bank csf_filters[5]; /*Offsets used to protect against division by 0.*/ float csf_offsets[3][64]; /*Sums of CSF-weighted masking values in each masking group over OC_MASK_WINDOW_SZ_MAX*2-1 rows, with OC_MASK_WINDOW_SZ_MAX-1 blocks of padding on either side.*/ oc_mask_block **mask_groups[3]; /*Individual CSF-weighted masking values over OC_MASK_WINDOW_SZ_MAX rows.*/ oc_weight_block **mask_weights[3]; /*The encoding context.*/ oc_enc_ctx *enc; /*The vertical delay for CSF filtering.*/ int vsize_max[3]; }; /*Computes a set of CSF filters to use for each plane given the current expected contrast level. _psych: The psycho-visual context. _contrast: The current expected contrast level.*/ static void oc_psych_csf_filters_interpolate(oc_psych_ctx *_psych, float _contrast){ float logc; int logc0; int logc1; float alpha; int hdec; int vdec; int i; int j; int k; hdec=!(_psych->enc->state.info.pixel_fmt&1); vdec=!(_psych->enc->state.info.pixel_fmt&2); /*Select a pair of CSF filters to interpolate over for the given contrast level. For contrasts in the range 0..1, we interpolate linearly. For contrasts in the range 1..64, we interpolate linearly in log space.*/ if(_contrast<=0){ logc0=logc1=0; alpha=0; } else if(_contrast<=1){ logc0=0; logc1=1; alpha=_contrast; } else if(_contrast<=64){ logc=OC_LOGF(_contrast)*OC_1_LN2+1; logc0=OC_IFLOORF(logc); logc1=OC_ICEILF(logc); alpha=logc-logc0; } else{ logc0=logc1=7; alpha=0; } for(i=0;i<8;i++){ for(j=0;j<5;j++){ /*Horizontal and vertical Y filter.*/ _psych->csf_filters[0][i][j]=OC_CSF_FILTERS[0][logc0][i][j]+ alpha*(OC_CSF_FILTERS[0][logc1][i][j]-OC_CSF_FILTERS[0][logc0][i][j]); /*Horizontal Cb filter.*/ _psych->csf_filters[1][i][j]=OC_CSF_FILTERS[1+hdec][logc0][i][j]+ alpha*(OC_CSF_FILTERS[1+hdec][logc1][i][j]- OC_CSF_FILTERS[1+hdec][logc0][i][j]); /*Vertical Cb filter.*/ _psych->csf_filters[2][i][j]=OC_CSF_FILTERS[1+vdec][logc0][i][j]+ alpha*(OC_CSF_FILTERS[1+vdec][logc1][i][j]- OC_CSF_FILTERS[1+vdec][logc0][i][j]); /*Horizontal Cr filter.*/ _psych->csf_filters[3][i][j]=OC_CSF_FILTERS[3+hdec][logc0][i][j]+ alpha*(OC_CSF_FILTERS[3+hdec][logc1][i][j]- OC_CSF_FILTERS[3+hdec][logc0][i][j]); /*Vertical Cr filter.*/ _psych->csf_filters[4][i][j]=OC_CSF_FILTERS[3+vdec][logc0][i][j]+ alpha*(OC_CSF_FILTERS[3+vdec][logc1][i][j]- OC_CSF_FILTERS[3+vdec][logc0][i][j]); } /*Find the filter length that leaves us with less than 1% error. For a filter size of n, the actual number of taps is 2*n+1.*/ for(k=0;k<5;k++){ for(j=OC_CSF_FILTER_SZ_MAX-1; OC_FABSF(_psych->csf_filters[k][i][0]*0.01F)> OC_FABSF(_psych->csf_filters[k][i][j]);j--); _psych->csf_filter_sizes[k][i]=j; } } /*Find the offsets used to protect against division by 0.*/ for(k=0;k<3;k++){ int vfilti; int hfilti; vfilti=k<<1; hfilti=k>0?(k<<1)-1:0; for(i=0;i<8;i++)for(j=0;j<8;j++){ int hi; int vi; _psych->csf_offsets[k][i<<3|j]=0; for(vi=1;vi<=_psych->csf_filter_sizes[vfilti][i];vi++){ for(hi=1;hi<=_psych->csf_filter_sizes[hfilti][j];hi++){ _psych->csf_offsets[k][i<<3|j]+=_psych->csf_filters[vfilti][i][vi]* _psych->csf_filters[hfilti][j][hi]; } } _psych->csf_offsets[k][i<<3|j]*=4; for(vi=1;vi<=_psych->csf_filter_sizes[vfilti][i];vi++){ _psych->csf_offsets[k][i<<3|j]+=_psych->csf_filters[vfilti][i][vi]*2* _psych->csf_filters[hfilti][j][0]; } for(hi=1;hi<=_psych->csf_filter_sizes[hfilti][j];hi++){ _psych->csf_offsets[k][i<<3|j]+=_psych->csf_filters[vfilti][i][0]*2* _psych->csf_filters[hfilti][j][hi]; } _psych->csf_offsets[k][i<<3|j]+=_psych->csf_filters[vfilti][i][0]* _psych->csf_filters[hfilti][j][0]; _psych->csf_offsets[k][i<<3|j]=OC_FABSF(_psych->csf_offsets[k][i<<3|j])* (i||j?OC_CSF_NOISE_LEVEL:OC_CSF_DC_SHIFT); } } } static int oc_csf_pipe_start(oc_enc_pipe_stage *_stage){ oc_psych_ctx *psych; int pli; psych=_stage->enc->vbr->psych; oc_psych_csf_filters_interpolate(psych,_stage->enc->vbr->qscale); for(pli=0;pli<3;pli++){ int *vsizes; int vfilti; int filti; _stage->y_procd[pli]=0; vfilti=pli<<1; vsizes=psych->csf_filter_sizes[vfilti]; /*Find the number of rows we have to perform DCTs in advance.*/ psych->vsize_max[pli]=0; for(filti=0;filti<8;filti++){ psych->vsize_max[pli]=OC_MAXI(psych->vsize_max[pli],vsizes[filti]); } } return _stage->next!=NULL?(*_stage->next->pipe_start)(_stage->next):0; } static int oc_csf_pipe_process(oc_enc_pipe_stage *_stage,int _y_avail[3]){ oc_psych_ctx *psych; int pli; psych=_stage->enc->vbr->psych; for(pli=0;pli<3;pli++){ int y_procd; int y_avail; /*Compute how far we can get in complete fragment rows.*/ y_procd=_stage->y_procd[pli]; /*Add a vsize_max[pli] delay.*/ y_avail=(_y_avail[pli]&~7)-(psych->vsize_max[pli]<<3); /*Perform CSF filtering on any newly available rows.*/ while(y_avail>y_procd){ oc_fragment_plane *fplane; oc_fragment *frags; oc_fragment_enc_info *efrags; oc_weight_block *maskw_row; oc_weight_block *csfw_row; oc_csf_filter *vfilters; oc_csf_filter *hfilters; oc_mask_block *maskg_row; float *csfw; float *maskw; float *maskg; int *vsizes; int *hsizes; int fragi_end; int fragx; int cfragi0; int cfragi; int cfragj; int mfragj; int vfilti; int hfilti; int filti; int ci; int wi; vfilti=pli<<1; vfilters=psych->csf_filters[vfilti]; vsizes=psych->csf_filter_sizes[vfilti]; hfilti=(pli<<1)-(pli>0); hfilters=psych->csf_filters[hfilti]; hsizes=psych->csf_filter_sizes[hfilti]; frags=psych->enc->state.frags; efrags=psych->enc->frinfo; fplane=psych->enc->state.fplanes+pli; fragi_end=fplane->froffset+fplane->nfrags; cfragi0=fplane->froffset+(y_procd>>3)*fplane->nhfrags; /*First, the vertical filter.*/ for(fragx=0,cfragi=cfragi0;fragxnhfrags;fragx++,cfragi++){ int fragi_off; csfw=psych->csf_row[fragx+OC_CSF_FILTER_SZ_MAX-1]; for(ci=0;ci<64;ci++){ filti=ci>>3; csfw[ci]=vfilters[filti][0]*efrags[cfragi].dct_coeffs[ci]; fragi_off=fplane->nhfrags; for(wi=vsizes[filti];wi>0;wi--){ int coeffs; cfragj=cfragi-fragi_off; coeffs=cfragj>=fplane->froffset?efrags[cfragj].dct_coeffs[ci]:0; cfragj=cfragi+fragi_off; if(cfragjnhfrags; } } } /*Next, the horizontal filtering.*/ maskg_row=psych->mask_groups[pli][OC_MASK_WINDOW_SZ_MAX-1<<1]; maskw_row=psych->mask_weights[pli][OC_MASK_WINDOW_SZ_MAX-1]; csfw_row=psych->csf_weights[pli][OC_MASK_WINDOW_SZ_MAX-1]; memset(maskg_row[0],0,sizeof(maskg_row[0])*( fplane->nhfrags+(OC_MASK_WINDOW_SZ_MAX-1<<1))); for(fragx=0,cfragi=cfragi0;fragxnhfrags;fragx++,cfragi++){ csfw=csfw_row[fragx]; if(frags[cfragi].invalid)memset(csfw,0,sizeof(oc_weight_block)); else{ cfragj=fragx+OC_CSF_FILTER_SZ_MAX-1; mfragj=fragx+OC_MASK_WINDOW_SZ_MAX-1; for(ci=0;ci<64;ci++){ filti=ci&7; csfw[ci]=hfilters[filti][0]*psych->csf_row[cfragj][ci]; for(wi=hsizes[filti];wi>0;wi--){ csfw[ci]+=0.5F*hfilters[filti][wi]*( psych->csf_row[cfragj-wi][ci]+psych->csf_row[cfragj+wi][ci]); } csfw[ci]=OC_FABSF(csfw[ci]); } maskw=maskw_row[fragx]; maskg=maskg_row[mfragj]; for(ci=1;ci<64;ci++){ maskw[ci]=OC_POWF(csfw[ci],OC_MASK_NU); maskg[OC_MASK_GROUP[ci]]+=maskw[ci]; } } } y_procd+=8; _stage->y_procd[pli]=y_procd; if(_stage->next!=NULL){ int ret; ret=(*_stage->next->pipe_proc)(_stage->next,_stage->y_procd); if(ret<0)return ret; } } } return 0; } static int oc_csf_pipe_end(oc_enc_pipe_stage *_stage){ oc_psych_ctx *psych; int pli; int ret; int y_avail[3]; psych=_stage->enc->vbr->psych; for(pli=0;pli<3;pli++){ y_avail[pli]=_stage->enc->state.input[pli].height+ (psych->vsize_max[pli]<<3); } ret=oc_csf_pipe_process(_stage,y_avail); if(ret<0)return ret; return _stage->next!=NULL?(*_stage->next->pipe_end)(_stage->next):0; } /*Initialize the CSF filter stage of the pipeline. _enc: The encoding context.*/ static void oc_csf_pipe_init(oc_enc_pipe_stage *_stage,oc_enc_ctx *_enc){ _stage->enc=_enc; _stage->next=NULL; _stage->pipe_start=oc_csf_pipe_start; _stage->pipe_proc=oc_csf_pipe_process; _stage->pipe_end=oc_csf_pipe_end; } static int oc_mask_pipe_start(oc_enc_pipe_stage *_stage){ oc_psych_ctx *psych; int pli; psych=_stage->enc->vbr->psych; for(pli=0;pli<3;pli++){ oc_fragment_plane *fplane; int rowi; _stage->y_procd[pli]=-(OC_MASK_WINDOW_SZ_MAX-1)<<3; fplane=psych->enc->state.fplanes+pli; /*Just clear out the mask group weights for the rows above the image.*/ for(rowi=OC_MASK_WINDOW_SZ_MAX-1;rowi<(OC_MASK_WINDOW_SZ_MAX-1<<1);rowi++){ memset(psych->mask_groups[pli][rowi][0],0, sizeof(oc_mask_block)*(fplane->nhfrags+(OC_MASK_WINDOW_SZ_MAX-1<<1))); } } return 0; } static int oc_mask_pipe_process(oc_enc_pipe_stage *_stage,int _y_avail[3]){ static const int o=OC_MASK_WINDOW_SZ_MAX-1; oc_psych_ctx *psych; int pli; psych=_stage->enc->vbr->psych; for(pli=0;pli<3;pli++){ int y_procd; int y_avail; /*Compute how far we can get in complete fragment rows.*/ y_procd=_stage->y_procd[pli]; /*Add an (OC_MASK_WINDOW_SZ_MAX-1) delay.*/ y_avail=(_y_avail[pli]&~7)-(o<<3); /*Perform CSF filtering on any newly available rows.*/ while(y_avail>y_procd){ oc_weight_block *csfw_row; oc_weight_block *maskw_row; oc_mask_block **mask_groups; oc_mask_block *maskg_row; int rowi; mask_groups=psych->mask_groups[pli]; if(y_procd>0){ oc_fragment_plane *fplane; oc_fragment_enc_info *efrag; oc_mask_block group_sums; float *csf_offset; float *csfw; float *maskw; float fscale; float pscale; int fragx; int mfragi; int mfragj; int qti; int gi; int wi; int wj; int ci; pscale=OC_YCbCr_SCALE[psych->enc->state.info.colorspace][pli]; csf_offset=psych->csf_offsets[pli]; maskw_row=psych->mask_weights[pli][0]; csfw_row=psych->csf_weights[pli][0]; for(gi=0;gienc->state.fplanes+pli; mfragi=fplane->froffset+(y_procd>>3)*fplane->nhfrags; for(fragx=0;fragxnhfrags;fragx++,mfragi++){ /*Add the parts of the group neighborhoods that are new.*/ mfragj=fragx+OC_MASK_WINDOW_SZ_MAX-1; for(gi=0;gienc->frinfo+mfragi; csfw=psych->csf_row[fragx]; maskw=maskw_row[fragx]; /*Compute the scaling value for this fragment.*/ fscale=pscale*efrag->imp_weight/psych->enc->vbr->qscale; /*The DC coefficient is not masked.*/ efrag->tols[0]=(ogg_uint16_t)OC_MINI(65535,(int)( (OC_CSF_DC_SHIFT+abs(efrag->dct_coeffs[0]))/ (fscale*(csf_offset[0]+csfw[0])))); psych->enc->vbr->dc_tol_mins[pli]=(unsigned)OC_MINI( psych->enc->vbr->dc_tol_mins[pli],efrag->tols[0]); /*The remaining coefficients are masked.*/ for(ci=1;ci<64;ci++){ float mask; gi=OC_MASK_GROUP[ci]; mask=group_sums[gi]-maskw[ci]; efrag->tols[ci]=(ogg_uint16_t)OC_MINI(65535,(int)( ((OC_CSF_NOISE_LEVEL+abs(efrag->dct_coeffs[ci]))* (1+OC_MASK_WEIGHTS[gi]*mask))/ (fscale*(csf_offset[ci]+csfw[ci])))); } /*Select minimum qi values for each quantizer type.*/ for(qti=2;qti-->0;){ unsigned qmin; int qi_min; /*This is the minimum quantizer Theora allows. Don't inflate the qi unnecessarily if we have a tolerance less than this.*/ qmin=OC_AC_QUANT_MIN[qti]; qi_min=qti?0:efrag->qi_min[1]; for(ci=0;qi_min<63;qi_min++){ while(psych->enc->state.dequant_tables[qti][pli][qi_min][ci]<= OC_MAXI(2U*efrag->tols[ci],qmin)&&++ci<64); if(ci==64)break; } efrag->qi_min[qti]=(unsigned char)qi_min; } #if 0 /*Now undo all the work we did above and just use a constant quantizer value for testing purposes.*/ efrag->qi_min[0]=efrag->qi_min[1]=_psych->enc->state.info.quality; for(ci=0;ci<64;ci++){ efrag->tols[ci]=OC_MINI( psych->enc->state.dequant_tables[0][pli][efrag->qi_min[0]][ci], psych->enc->state.dequant_tables[1][pli][efrag->qi_min[1]][ci])+ 1>>1; } #endif /*Remove the parts of the group neighborhoods that are old.*/ for(gi=0;gimask_weights[pli][0]; csfw_row=psych->csf_weights[pli][0]; for(rowi=0;rowimask_weights[pli][rowi]=psych->mask_weights[pli][rowi+1]; psych->csf_weights[pli][rowi]=psych->csf_weights[pli][rowi+1]; } psych->mask_weights[pli][OC_MASK_WINDOW_SZ_MAX-1]=maskw_row; psych->csf_weights[pli][OC_MASK_WINDOW_SZ_MAX-1]=csfw_row; y_procd+=8; } _stage->y_procd[pli]=y_procd; } return 0; } static int oc_mask_pipe_end(oc_enc_pipe_stage *_stage){ oc_psych_ctx *psych; int y_avail[3]; int rowi; int pli; int ret; psych=_stage->enc->vbr->psych; /*To finish up this stage, we need to keep the sliding windows sliding, so we make a separate call to pipe_process() for each row.*/ for(pli=0;pli<3;pli++)y_avail[pli]=_stage->enc->state.input[pli].height; for(rowi=OC_MASK_WINDOW_SZ_MAX-1;rowi-->0;){ for(pli=0;pli<3;pli++){ oc_fragment_plane *fplane; fplane=psych->enc->state.fplanes+pli; /*Just clear out the mask group weights for the rows below the image.*/ memset(psych->mask_groups[pli][OC_MASK_WINDOW_SZ_MAX-1<<1][0],0, sizeof(oc_mask_block)*(fplane->nhfrags+(OC_MASK_WINDOW_SZ_MAX-1<<1))); y_avail[pli]+=8; } ret=oc_mask_pipe_process(_stage,y_avail); if(ret<0)return ret; } /*Because dc_tol_mins[] is not complete until each plane has been completely processed, we wait until then to execute the next pipeline stage.*/ if(_stage->next!=NULL){ ret=(*_stage->next->pipe_start)(_stage->next); if(ret<0)return ret; ret=(*_stage->next->pipe_proc)(_stage->next,_stage->y_procd); if(ret<0)return ret; return (*_stage->next->pipe_end)(_stage->next); } return 0; } /*Initialize the spatial masking stage of the pipeline. _enc: The encoding context.*/ static void oc_mask_pipe_init(oc_enc_pipe_stage *_stage,oc_enc_ctx *_enc){ _stage->enc=_enc; _stage->next=NULL; _stage->pipe_start=oc_mask_pipe_start; _stage->pipe_proc=oc_mask_pipe_process; _stage->pipe_end=oc_mask_pipe_end; } #if 0 static void oc_psych_scan_plane(oc_psych_ctx *_psych,int _pli){ oc_csf_filter *vfilters; oc_csf_filter *hfilters; oc_fragment_plane *fplane; oc_fragment *frags; oc_fragment_enc_info *efrags; oc_fragment_enc_info *efrag; oc_mask_block *maskg_row; oc_weight_block *maskw_row; oc_weight_block *csfw_row; float *csfw; float *maskw; float *maskg; float *csf_offset; float pscale; float fscale; int *vsizes; int *hsizes; int vfilti; int hfilti; int filti; int vsize_max; int ystride; int fragx; int fragy; int fragy_end; int fragi_end; int dfragi; int cfragi; int cfragj; int mfragi; int mfragj; int qti; int i; int j; int k; csf_offset=_psych->csf_offsets[_pli]; /*Initialize the minimum psychovisual tolerance for the DC coefficient.*/ _psych->enc->vbr->dc_tol_mins[_pli]=32767; /*Select the filter sets we're going to use.*/ vfilti=_pli<<1; hfilti=_pli>0?(_pli<<1)-1:0; vfilters=_psych->csf_filters[vfilti]; vsizes=_psych->csf_filter_sizes[vfilti]; hfilters=_psych->csf_filters[hfilti]; hsizes=_psych->csf_filter_sizes[hfilti]; /*Find the number of rows we have to perform DCTs in advance.*/ vsize_max=0; for(i=0;i<8;i++)if(vsizes[i]>vsize_max)vsize_max=vsizes[i]; fplane=_psych->enc->state.fplanes+_pli; frags=_psych->enc->state.frags; efrags=_psych->enc->frinfo; ystride=_psych->enc->state.input[_pli].ystride; pscale=OC_YCbCr_SCALE[_psych->enc->state.info.colorspace][_pli]; /*Clear out the mask group weights for the rows above the image.*/ for(i=(OC_MASK_WINDOW_SZ_MAX-2<<1)-vsize_max;imask_groups[i][0],0,sizeof(oc_mask_block)*( fplane->nhfrags+(OC_MASK_WINDOW_SZ_MAX-1<<1))); } /*Run each of the filters over the rows, with a suitable delay between each.*/ fragy_end=fplane->nvfrags+vsize_max+OC_MASK_WINDOW_SZ_MAX-1; fragi_end=fplane->froffset+fplane->nfrags; dfragi=cfragi=mfragi=fplane->froffset; for(fragy=0;fragynvfrags){ for(fragx=0;fragxnhfrags;fragx++,dfragi++){ if(frags[dfragi].invalid)continue; oc_enc_frag_intra_fdct(_psych->enc, frags+dfragi,efrags[dfragi].dct_coeffs,ystride,OC_FRAME_IO); } } /*After a vsize_max delay, perform CSF filtering.*/ if(fragy>=vsize_max&&fragynvfrags+vsize_max){ int chfragi; /*First, the vertical filter.*/ for(fragx=0,chfragi=cfragi;fragxnhfrags;fragx++,chfragi++){ int fragi_off; csfw=_psych->csf_row[fragx+OC_CSF_FILTER_SZ_MAX-1]; for(i=0;i<64;i++){ filti=i>>3; csfw[i]=vfilters[filti][0]*efrags[chfragi].dct_coeffs[i]; fragi_off=fplane->nhfrags; for(j=vsizes[filti];j>0;j--){ int coeffs; int cvfragi; cvfragi=chfragi-fragi_off; coeffs=cvfragi>=fplane->froffset?efrags[cvfragi].dct_coeffs[i]:0; cvfragi=chfragi+fragi_off; if(cvfraginhfrags; } } } /*Next, the horizontal filtering.*/ maskg_row=_psych->mask_groups[OC_MASK_WINDOW_SZ_MAX-1<<1]; maskw_row=_psych->mask_weights[OC_MASK_WINDOW_SZ_MAX-1]; csfw_row=_psych->csf_weights[OC_MASK_WINDOW_SZ_MAX-1]; memset(maskg_row[0],0,sizeof(maskg_row[0])*( fplane->nhfrags+(OC_MASK_WINDOW_SZ_MAX-1<<1))); for(fragx=0;fragxnhfrags;fragx++,cfragi++){ csfw=csfw_row[fragx]; if(frags[cfragi].invalid)memset(csfw,0,sizeof(oc_weight_block)); else{ cfragj=fragx+OC_CSF_FILTER_SZ_MAX-1; mfragj=fragx+OC_MASK_WINDOW_SZ_MAX-1; for(i=0;i<64;i++){ filti=i&7; csfw[i]=hfilters[filti][0]*_psych->csf_row[cfragj][i]; for(j=hsizes[filti];j>0;j--){ csfw[i]+=0.5F*hfilters[filti][j]*( _psych->csf_row[cfragj-j][i]+_psych->csf_row[cfragj+j][i]); } csfw[i]=OC_FABSF(csfw[i]); } maskw=maskw_row[fragx]; maskg=maskg_row[mfragj]; for(i=1;i<64;i++){ maskw[i]=OC_POWF(csfw[i],OC_MASK_NU); maskg[OC_MASK_GROUP[i]]+=maskw[i]; } } } } else{ /*Clear out the mask group weights for the rows below the image.*/ memset(_psych->mask_groups[OC_MASK_WINDOW_SZ_MAX-1<<1][0],0, sizeof(oc_mask_block)*(fplane->nhfrags+(OC_MASK_WINDOW_SZ_MAX-1<<1))); } /*After an additional OC_MASK_WINDOW_SZ_MAX delay, perform neighborhood masking.*/ if(fragy>=vsize_max+OC_MASK_WINDOW_SZ_MAX-1){ static const int o=OC_MASK_WINDOW_SZ_MAX-1; oc_mask_block group_sums; for(i=0;imask_groups[j][k][i]; } } } maskw_row=_psych->mask_weights[0]; for(fragx=0;fragxnhfrags;fragx++,mfragi++){ /*Add the parts of the group neighborhoods that are new.*/ mfragj=fragx+OC_MASK_WINDOW_SZ_MAX-1; for(i=0;imask_groups[j][mfragj+k][i]; } } /*These groups are so small it is not worth incremental updates.*/ for(;imask_groups[o][mfragj][i]+ _psych->mask_groups[o-1][mfragj][i]+ _psych->mask_groups[o+1][mfragj][i]+ _psych->mask_groups[o][mfragj-1][i]+ _psych->mask_groups[o][mfragj+1][i]; } /*Mask the coefficients in this block.*/ /*An offset is added to the numerator and denominator to prevent against division by zero. In effect, this is like a small addition of noise to the signal, but it should be well below the visual threshold. This is the best solution I could come up with to handle the problem of 0's in the inter DCT coefficients that, because of motion compensation, are not 0 in the intra DCT coefficients and thus need a valid weight. The DC coefficient has a different offset added than the others, because the pixel values have 128 subtracted from them before the DCT is performed, which offsets the DC coefficient by 4096.*/ efrag=efrags+mfragi; csfw=_psych->csf_weights[0][fragx]; maskw=maskw_row[fragx]; /*Compute the scaling value for this fragment.*/ fscale=pscale*efrag->imp_weight/_psych->enc->vbr->qscale; /*The DC coefficient is not masked.*/ efrag->tols[0]=(ogg_uint16_t)OC_MINI(65535,(int)( (OC_CSF_DC_SHIFT+abs(efrag->dct_coeffs[0]))/ (fscale*(csf_offset[0]+csfw[0])))); _psych->enc->vbr->dc_tol_mins[_pli]=(unsigned)OC_MINI( _psych->enc->vbr->dc_tol_mins[_pli],efrag->tols[0]); /*The remaining coefficients are masked.*/ for(i=1;i<64;i++){ float mask; int groupi; groupi=OC_MASK_GROUP[i]; mask=group_sums[groupi]-maskw[i]; efrag->tols[i]=(ogg_uint16_t)OC_MINI(65535,(int)( ((OC_CSF_NOISE_LEVEL+abs(efrag->dct_coeffs[i]))* (1+OC_MASK_WEIGHTS[groupi]*mask))/ (fscale*(csf_offset[i]+csfw[i])))); } /*Select minimum qi values for each quantizer type.*/ for(qti=2;qti-->0;){ unsigned qmin; int qi_min; /*This is the minimum quantizer Theora allows. Don't inflate the qi unnecessarily if we have a tolerance less than this.*/ qmin=OC_AC_QUANT_MIN[qti]; qi_min=qti?0:efrag->qi_min[1]; for(i=0;qi_min<63;qi_min++){ while(_psych->enc->state.dequant_tables[qti][_pli][qi_min][i]<= OC_MAXI(2U*efrag->tols[i],qmin)&&++i<64); if(i==64)break; } efrag->qi_min[qti]=(unsigned char)qi_min; } #if 0 /*Now undo all the work we did above and just use a constant quantizer value for testing purposes.*/ efrag->qi_min[0]=efrag->qi_min[1]=_psych->enc->state.info.quality; for(i=0;i<64;i++){ efrag->tols[i]=OC_MINI( _psych->enc->state.dequant_tables[0][_pli][efrag->qi_min[0]][i], _psych->enc->state.dequant_tables[1][_pli][efrag->qi_min[1]][i])+ 1>>1; } #endif /*Remove the parts of the group neighborhoods that are old.*/ for(i=0;imask_groups[j][mfragj-k][i]; } } } } /*Move the sliding windows. The nice thing about these manually allocated 2D arrays is that we can move rows around just by moving around the initial pointers to them, not actually copying their contents.*/ maskg_row=_psych->mask_groups[0]; for(i=0;imask_groups[i]=_psych->mask_groups[i+1]; } _psych->mask_groups[OC_MASK_WINDOW_SZ_MAX-1<<1]=maskg_row; maskw_row=_psych->mask_weights[0]; csfw_row=_psych->csf_weights[0]; for(i=0;imask_weights[i]=_psych->mask_weights[i+1]; _psych->csf_weights[i]=_psych->csf_weights[i+1]; } _psych->mask_weights[OC_MASK_WINDOW_SZ_MAX-1]=maskw_row; _psych->csf_weights[OC_MASK_WINDOW_SZ_MAX-1]=csfw_row; } } #endif oc_psych_ctx *oc_psych_alloc(oc_enc_ctx *_enc){ oc_psych_ctx *psych; int nhfrags; int pli; nhfrags=_enc->state.fplanes[0].nhfrags; psych=(oc_psych_ctx *)_ogg_malloc(sizeof(*psych)); psych->csf_row=(oc_weight_block *)_ogg_calloc( (nhfrags+(OC_CSF_FILTER_SZ_MAX-1<<1)),sizeof(psych->csf_row[0])); for(pli=0;pli<3;pli++){ nhfrags=_enc->state.fplanes[pli].nhfrags; psych->csf_weights[pli]=(oc_weight_block **)oc_malloc_2d( OC_MASK_WINDOW_SZ_MAX,nhfrags,sizeof(oc_weight_block)); psych->mask_groups[pli]=(oc_mask_block **)oc_malloc_2d( (OC_MASK_WINDOW_SZ_MAX<<1)-1,nhfrags+(OC_MASK_WINDOW_SZ_MAX-1<<1), sizeof(oc_mask_block)); psych->mask_weights[pli]=(oc_weight_block **)oc_malloc_2d( OC_MASK_WINDOW_SZ_MAX,nhfrags,sizeof(oc_weight_block)); } psych->enc=_enc; /*Initialize our pipeline stages.*/ oc_csf_pipe_init(&psych->csf_pipe,_enc); oc_mask_pipe_init(&psych->mask_pipe,_enc); psych->csf_pipe.next=&psych->mask_pipe; return psych; } void oc_psych_free(oc_psych_ctx *_psych){ if(_psych!=NULL){ int pli; _ogg_free(_psych->csf_row); for(pli=0;pli<3;pli++){ oc_free_2d((void **)_psych->csf_weights[pli]); oc_free_2d((void **)_psych->mask_groups[pli]); oc_free_2d((void **)_psych->mask_weights[pli]); } _ogg_free(_psych); } } oc_enc_pipe_stage *oc_psych_prepend_to_pipe(oc_psych_ctx *_psych, oc_enc_pipe_stage *_next){ _psych->mask_pipe.next=_next; return &_psych->csf_pipe; } #if 0 void oc_psych_scan(oc_psych_ctx *_psych,float _contrast){ int pli; oc_psych_csf_filters_interpolate(_psych,_contrast); for(pli=0;pli<3;pli++)oc_psych_scan_plane(_psych,pli); } #endif