123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484248524862487248824892490249124922493249424952496249724982499250025012502250325042505250625072508250925102511251225132514251525162517251825192520252125222523252425252526252725282529253025312532253325342535253625372538253925402541254225432544254525462547254825492550255125522553255425552556255725582559256025612562256325642565256625672568256925702571257225732574257525762577257825792580258125822583258425852586258725882589259025912592259325942595259625972598259926002601260226032604260526062607260826092610261126122613261426152616261726182619262026212622262326242625262626272628262926302631263226332634263526362637263826392640264126422643264426452646264726482649265026512652265326542655265626572658265926602661266226632664266526662667266826692670267126722673267426752676267726782679268026812682268326842685268626872688268926902691269226932694269526962697269826992700270127022703270427052706270727082709271027112712271327142715271627172718271927202721272227232724272527262727272827292730273127322733273427352736273727382739274027412742274327442745274627472748274927502751275227532754275527562757275827592760276127622763276427652766276727682769277027712772277327742775277627772778277927802781278227832784278527862787278827892790279127922793279427952796279727982799280028012802280328042805280628072808280928102811281228132814281528162817281828192820282128222823282428252826282728282829283028312832283328342835283628372838283928402841284228432844284528462847284828492850285128522853285428552856285728582859286028612862286328642865286628672868286928702871287228732874287528762877287828792880288128822883288428852886288728882889289028912892289328942895289628972898289929002901290229032904290529062907290829092910291129122913291429152916291729182919292029212922292329242925292629272928292929302931293229332934293529362937293829392940294129422943294429452946294729482949295029512952295329542955295629572958295929602961296229632964296529662967296829692970297129722973297429752976297729782979298029812982298329842985298629872988298929902991299229932994299529962997299829993000300130023003300430053006300730083009301030113012301330143015301630173018301930203021302230233024302530263027302830293030303130323033303430353036303730383039304030413042304330443045304630473048304930503051305230533054305530563057305830593060306130623063306430653066306730683069307030713072307330743075307630773078307930803081308230833084308530863087308830893090309130923093309430953096309730983099310031013102310331043105310631073108310931103111311231133114311531163117311831193120312131223123312431253126312731283129313031313132313331343135313631373138313931403141314231433144314531463147314831493150315131523153315431553156315731583159316031613162316331643165316631673168316931703171317231733174317531763177317831793180318131823183318431853186318731883189319031913192319331943195319631973198319932003201320232033204320532063207320832093210321132123213321432153216321732183219322032213222322332243225322632273228322932303231323232333234323532363237323832393240324132423243324432453246324732483249325032513252325332543255325632573258325932603261326232633264326532663267326832693270327132723273327432753276327732783279328032813282328332843285328632873288328932903291329232933294329532963297329832993300330133023303330433053306330733083309331033113312331333143315331633173318331933203321332233233324332533263327332833293330333133323333333433353336333733383339334033413342334333443345334633473348334933503351335233533354335533563357335833593360336133623363336433653366336733683369337033713372337333743375337633773378337933803381338233833384338533863387338833893390339133923393339433953396339733983399340034013402340334043405340634073408340934103411341234133414341534163417341834193420342134223423342434253426342734283429343034313432343334343435343634373438343934403441344234433444344534463447344834493450345134523453345434553456345734583459346034613462346334643465346634673468346934703471347234733474347534763477347834793480348134823483348434853486348734883489349034913492349334943495349634973498349935003501350235033504350535063507350835093510351135123513351435153516351735183519352035213522352335243525352635273528352935303531353235333534353535363537353835393540354135423543354435453546354735483549355035513552355335543555355635573558355935603561356235633564356535663567356835693570357135723573357435753576357735783579358035813582358335843585358635873588358935903591359235933594359535963597359835993600360136023603360436053606360736083609361036113612361336143615361636173618361936203621362236233624362536263627362836293630363136323633363436353636363736383639364036413642364336443645364636473648364936503651365236533654365536563657365836593660366136623663366436653666366736683669367036713672367336743675367636773678367936803681368236833684368536863687368836893690369136923693369436953696369736983699370037013702370337043705370637073708370937103711371237133714371537163717371837193720372137223723372437253726372737283729373037313732373337343735373637373738373937403741374237433744374537463747374837493750375137523753375437553756375737583759376037613762376337643765376637673768376937703771377237733774377537763777377837793780378137823783378437853786378737883789379037913792379337943795379637973798379938003801380238033804380538063807380838093810381138123813381438153816381738183819382038213822382338243825382638273828382938303831383238333834383538363837383838393840384138423843384438453846384738483849385038513852385338543855385638573858385938603861386238633864386538663867386838693870387138723873387438753876387738783879388038813882388338843885388638873888388938903891389238933894389538963897389838993900390139023903390439053906390739083909391039113912391339143915391639173918391939203921392239233924392539263927392839293930393139323933393439353936393739383939394039413942394339443945394639473948394939503951395239533954395539563957395839593960396139623963396439653966396739683969397039713972397339743975397639773978397939803981398239833984398539863987398839893990399139923993399439953996399739983999400040014002400340044005400640074008400940104011401240134014401540164017401840194020402140224023402440254026402740284029403040314032403340344035403640374038403940404041404240434044404540464047404840494050405140524053405440554056405740584059406040614062406340644065406640674068406940704071407240734074407540764077407840794080408140824083408440854086408740884089409040914092409340944095409640974098409941004101410241034104410541064107410841094110411141124113411441154116411741184119412041214122412341244125412641274128412941304131413241334134413541364137413841394140414141424143414441454146414741484149415041514152415341544155415641574158415941604161416241634164416541664167416841694170417141724173417441754176417741784179418041814182418341844185418641874188418941904191419241934194419541964197419841994200420142024203420442054206420742084209421042114212421342144215421642174218421942204221422242234224422542264227422842294230423142324233423442354236423742384239424042414242424342444245424642474248424942504251425242534254425542564257425842594260426142624263426442654266426742684269427042714272427342744275427642774278427942804281428242834284428542864287428842894290429142924293429442954296429742984299430043014302430343044305430643074308430943104311431243134314431543164317431843194320432143224323432443254326432743284329433043314332433343344335433643374338433943404341434243434344434543464347434843494350435143524353435443554356435743584359436043614362436343644365436643674368436943704371437243734374437543764377437843794380438143824383438443854386438743884389439043914392439343944395439643974398439944004401440244034404440544064407440844094410441144124413441444154416441744184419442044214422442344244425442644274428442944304431443244334434443544364437443844394440444144424443444444454446444744484449445044514452445344544455445644574458445944604461446244634464446544664467446844694470447144724473447444754476447744784479448044814482448344844485448644874488448944904491449244934494449544964497449844994500450145024503450445054506450745084509451045114512451345144515451645174518451945204521452245234524452545264527452845294530453145324533453445354536453745384539454045414542454345444545454645474548454945504551455245534554455545564557455845594560456145624563456445654566456745684569457045714572457345744575457645774578457945804581458245834584458545864587458845894590459145924593459445954596459745984599460046014602460346044605460646074608460946104611461246134614461546164617461846194620462146224623462446254626462746284629463046314632463346344635463646374638463946404641464246434644464546464647464846494650465146524653 |
- // Copyright 2016 Hajime Hoshi
- //
- // Licensed under the Apache License, Version 2.0 (the "License");
- // you may not use this file except in compliance with the License.
- // You may obtain a copy of the License at
- //
- // http://www.apache.org/licenses/LICENSE-2.0
- //
- // Unless required by applicable law or agreed to in writing, software
- // distributed under the License is distributed on an "AS IS" BASIS,
- // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- // See the License for the specific language governing permissions and
- // limitations under the License.
- package ebiten_test
- import (
- "bytes"
- "fmt"
- "image"
- "image/color"
- "image/draw"
- _ "image/png"
- "math"
- "math/rand/v2"
- "runtime"
- "testing"
- "github.com/hajimehoshi/ebiten/v2"
- "github.com/hajimehoshi/ebiten/v2/examples/resources/images"
- "github.com/hajimehoshi/ebiten/v2/internal/graphics"
- t "github.com/hajimehoshi/ebiten/v2/internal/testing"
- "github.com/hajimehoshi/ebiten/v2/internal/ui"
- )
- // maxImageSize is a maximum image size that should work in almost every environment.
- const maxImageSize = 4096 - 2
- func skipTooSlowTests(t *testing.T) bool {
- if testing.Short() {
- t.Skip("skipping test in short mode")
- return true
- }
- if runtime.GOOS == "js" {
- t.Skip("too slow or fragile on Wasm")
- return true
- }
- return false
- }
- func TestMain(m *testing.M) {
- ui.SetPanicOnErrorOnReadingPixelsForTesting(true)
- t.MainWithRunLoop(m)
- }
- func openEbitenImage() (*ebiten.Image, image.Image, error) {
- img, _, err := image.Decode(bytes.NewReader(images.Ebiten_png))
- if err != nil {
- return nil, nil, err
- }
- eimg := ebiten.NewImageFromImage(img)
- return eimg, img, nil
- }
- func abs(x int) int {
- if x < 0 {
- return -x
- }
- return x
- }
- // sameColors compares c1 and c2 and returns a boolean value indicating
- // if the two colors are (almost) same.
- //
- // Pixels read from GPU might include errors (#492), and
- // sameColors considers such errors as delta.
- func sameColors(c1, c2 color.RGBA, delta int) bool {
- return abs(int(c1.R)-int(c2.R)) <= delta &&
- abs(int(c1.G)-int(c2.G)) <= delta &&
- abs(int(c1.B)-int(c2.B)) <= delta &&
- abs(int(c1.A)-int(c2.A)) <= delta
- }
- func TestImagePixels(t *testing.T) {
- img0, img, err := openEbitenImage()
- if err != nil {
- t.Fatal(err)
- return
- }
- if got := img0.Bounds().Size(); got != img.Bounds().Size() {
- t.Fatalf("img size: got %d; want %d", got, img.Bounds().Size())
- }
- w, h := img0.Bounds().Dx(), img0.Bounds().Dy()
- // Check out of range part
- w2, h2 := graphics.InternalImageSize(w), graphics.InternalImageSize(h)
- for j := -100; j < h2+100; j++ {
- for i := -100; i < w2+100; i++ {
- got := img0.At(i, j)
- want := color.RGBAModel.Convert(img.At(i, j))
- if got != want {
- t.Errorf("img0 At(%d, %d): got %v; want %v", i, j, got, want)
- }
- }
- }
- pix := make([]byte, 4*w*h)
- img0.ReadPixels(pix)
- for j := 0; j < h; j++ {
- for i := 0; i < w; i++ {
- idx := 4 * (j*w + i)
- got := color.RGBA{R: pix[idx], G: pix[idx+1], B: pix[idx+2], A: pix[idx+3]}
- want := color.RGBAModel.Convert(img.At(i, j))
- if got != want {
- t.Errorf("(%d, %d): got %v; want %v", i, j, got, want)
- }
- }
- }
- }
- func TestImageComposition(t *testing.T) {
- img2Color := color.NRGBA{R: 0x24, G: 0x3f, B: 0x6a, A: 0x88}
- img3Color := color.NRGBA{R: 0x85, G: 0xa3, B: 0x08, A: 0xd3}
- // TODO: Rename this to img0
- img1, _, err := openEbitenImage()
- if err != nil {
- t.Fatal(err)
- return
- }
- w, h := img1.Bounds().Dx(), img1.Bounds().Dy()
- img2 := ebiten.NewImage(w, h)
- img3 := ebiten.NewImage(w, h)
- img2.Fill(img2Color)
- img3.Fill(img3Color)
- img_12_3 := ebiten.NewImage(w, h)
- img2.DrawImage(img1, nil)
- img3.DrawImage(img2, nil)
- img_12_3.DrawImage(img3, nil)
- img2.Fill(img2Color)
- img3.Fill(img3Color)
- img_1_23 := ebiten.NewImage(w, h)
- img3.DrawImage(img2, nil)
- img3.DrawImage(img1, nil)
- img_1_23.DrawImage(img3, nil)
- for j := 0; j < h; j++ {
- for i := 0; i < w; i++ {
- c1 := img_12_3.At(i, j).(color.RGBA)
- c2 := img_1_23.At(i, j).(color.RGBA)
- if !sameColors(c1, c2, 1) {
- t.Errorf("img_12_3.At(%d, %d) = %v; img_1_23.At(%[1]d, %[2]d) = %#[4]v", i, j, c1, c2)
- }
- if c1.A == 0 {
- t.Fatalf("img_12_3.At(%d, %d).A = 0; nothing is rendered?", i, j)
- }
- if c2.A == 0 {
- t.Fatalf("img_1_23.At(%d, %d).A = 0; nothing is rendered?", i, j)
- }
- }
- }
- }
- func TestImageSelf(t *testing.T) {
- // Note that mutex usages: without defer, unlocking is not called when panicking.
- defer func() {
- if r := recover(); r == nil {
- t.Errorf("DrawImage must panic but not")
- }
- }()
- img, _, err := openEbitenImage()
- if err != nil {
- t.Fatal(err)
- return
- }
- img.DrawImage(img, nil)
- }
- func TestImageScale(t *testing.T) {
- for _, scale := range []int{2, 3, 4} {
- img0, _, err := openEbitenImage()
- if err != nil {
- t.Fatal(err)
- return
- }
- w, h := img0.Bounds().Dx(), img0.Bounds().Dy()
- img1 := ebiten.NewImage(w*scale, h*scale)
- op := &ebiten.DrawImageOptions{}
- op.GeoM.Scale(float64(scale), float64(scale))
- img1.DrawImage(img0, op)
- for j := 0; j < h*scale; j++ {
- for i := 0; i < w*scale; i++ {
- c0 := img0.At(i/scale, j/scale).(color.RGBA)
- c1 := img1.At(i, j).(color.RGBA)
- if c0 != c1 {
- t.Fatalf("img0.At(%[1]d, %[2]d) should equal to img1.At(%[3]d, %[4]d) (with scale %[5]d) but not: %[6]v vs %[7]v", i/2, j/2, i, j, scale, c0, c1)
- }
- }
- }
- }
- }
- func TestImage90DegreeRotate(t *testing.T) {
- img0, _, err := openEbitenImage()
- if err != nil {
- t.Fatal(err)
- return
- }
- w, h := img0.Bounds().Dx(), img0.Bounds().Dy()
- img1 := ebiten.NewImage(h, w)
- op := &ebiten.DrawImageOptions{}
- op.GeoM.Rotate(math.Pi / 2)
- op.GeoM.Translate(float64(h), 0)
- img1.DrawImage(img0, op)
- for j := 0; j < h; j++ {
- for i := 0; i < w; i++ {
- c0 := img0.At(i, j).(color.RGBA)
- c1 := img1.At(h-j-1, i).(color.RGBA)
- if c0 != c1 {
- t.Errorf("img0.At(%[1]d, %[2]d) should equal to img1.At(%[3]d, %[4]d) but not: %[5]v vs %[6]v", i, j, h-j-1, i, c0, c1)
- }
- }
- }
- }
- func TestImageDotByDotInversion(t *testing.T) {
- img0, _, err := openEbitenImage()
- if err != nil {
- t.Fatal(err)
- return
- }
- w, h := img0.Bounds().Dx(), img0.Bounds().Dy()
- img1 := ebiten.NewImage(w, h)
- op := &ebiten.DrawImageOptions{}
- op.GeoM.Rotate(math.Pi)
- op.GeoM.Translate(float64(w), float64(h))
- img1.DrawImage(img0, op)
- for j := 0; j < h; j++ {
- for i := 0; i < w; i++ {
- c0 := img0.At(i, j).(color.RGBA)
- c1 := img1.At(w-i-1, h-j-1).(color.RGBA)
- if c0 != c1 {
- t.Errorf("img0.At(%[1]d, %[2]d) should equal to img1.At(%[3]d, %[4]d) but not: %[5]v vs %[6]v", i, j, w-i-1, h-j-1, c0, c1)
- }
- }
- }
- }
- func TestImageWritePixels(t *testing.T) {
- // Create a dummy image so that the shared texture is used and origImg's position is shifted.
- dummyImg := ebiten.NewImageFromImage(image.NewRGBA(image.Rect(0, 0, 16, 16)))
- defer dummyImg.Deallocate()
- _, origImg, err := openEbitenImage()
- if err != nil {
- t.Fatal(err)
- return
- }
- // Convert to *image.RGBA just in case.
- img := image.NewRGBA(origImg.Bounds())
- draw.Draw(img, img.Bounds(), origImg, image.ZP, draw.Src)
- size := img.Bounds().Size()
- img0 := ebiten.NewImage(size.X, size.Y)
- img0.WritePixels(img.Pix)
- for j := 0; j < img0.Bounds().Dy(); j++ {
- for i := 0; i < img0.Bounds().Dx(); i++ {
- got := img0.At(i, j)
- want := img.At(i, j)
- if got != want {
- t.Errorf("img0 At(%d, %d): got %v; want %v", i, j, got, want)
- }
- }
- }
- p := make([]uint8, 4*size.X*size.Y)
- for i := range p {
- p[i] = 0x80
- }
- img0.WritePixels(p)
- // Even if p is changed after calling ReplacePixel, img0 uses the original values.
- for i := range p {
- p[i] = 0
- }
- for j := 0; j < img0.Bounds().Dy(); j++ {
- for i := 0; i < img0.Bounds().Dx(); i++ {
- got := img0.At(i, j)
- want := color.RGBA{R: 0x80, G: 0x80, B: 0x80, A: 0x80}
- if got != want {
- t.Errorf("img0 At(%d, %d): got %v; want %v", i, j, got, want)
- }
- }
- }
- }
- func TestImageWritePixelsNil(t *testing.T) {
- defer func() {
- if r := recover(); r == nil {
- t.Errorf("WritePixels(nil) must panic")
- }
- }()
- img := ebiten.NewImage(16, 16)
- img.Fill(color.White)
- img.WritePixels(nil)
- }
- func TestImageDispose(t *testing.T) {
- img := ebiten.NewImage(16, 16)
- img.Fill(color.White)
- img.Dispose()
- // The color is transparent (color.RGBA{}).
- // Note that the value's type must be color.RGBA.
- got := img.At(0, 0)
- want := color.RGBA{}
- if got != want {
- t.Errorf("img.At(0, 0) got: %v, want: %v", got, want)
- }
- }
- func TestImageDeallocate(t *testing.T) {
- img := ebiten.NewImage(16, 16)
- img.Fill(color.White)
- img.Deallocate()
- // The color is transparent (color.RGBA{}).
- got := img.At(0, 0)
- want := color.RGBA{}
- if got != want {
- t.Errorf("img.At(0, 0) got: %v, want: %v", got, want)
- }
- }
- func TestImageBlendLighter(t *testing.T) {
- img0, _, err := openEbitenImage()
- if err != nil {
- t.Fatal(err)
- return
- }
- w, h := img0.Bounds().Dx(), img0.Bounds().Dy()
- img1 := ebiten.NewImage(w, h)
- img1.Fill(color.RGBA{R: 0x01, G: 0x02, B: 0x03, A: 0x04})
- op := &ebiten.DrawImageOptions{}
- op.Blend = ebiten.BlendLighter
- img1.DrawImage(img0, op)
- for j := 0; j < img1.Bounds().Dy(); j++ {
- for i := 0; i < img1.Bounds().Dx(); i++ {
- got := img1.At(i, j).(color.RGBA)
- want := img0.At(i, j).(color.RGBA)
- want.R = uint8(min(0xff, int(want.R)+1))
- want.G = uint8(min(0xff, int(want.G)+2))
- want.B = uint8(min(0xff, int(want.B)+3))
- want.A = uint8(min(0xff, int(want.A)+4))
- if got != want {
- t.Errorf("img1 At(%d, %d): got %v; want %v", i, j, got, want)
- }
- }
- }
- }
- func TestNewImageFromEbitenImage(t *testing.T) {
- img, _, err := openEbitenImage()
- if err != nil {
- t.Fatal(err)
- return
- }
- _ = ebiten.NewImageFromImage(img)
- }
- func TestNewImageFromSubImage(t *testing.T) {
- _, img, err := openEbitenImage()
- if err != nil {
- t.Fatal(err)
- return
- }
- w, h := img.Bounds().Dx(), img.Bounds().Dy()
- subImg := img.(*image.NRGBA).SubImage(image.Rect(1, 1, w-1, h-1))
- eimg := ebiten.NewImageFromImage(subImg)
- sw, sh := subImg.Bounds().Dx(), subImg.Bounds().Dy()
- w2, h2 := eimg.Bounds().Dx(), eimg.Bounds().Dy()
- if w2 != sw {
- t.Errorf("eimg Width: got %v; want %v", w2, sw)
- }
- if h2 != sh {
- t.Errorf("eimg Width: got %v; want %v", h2, sh)
- }
- for j := 0; j < h2; j++ {
- for i := 0; i < w2; i++ {
- got := eimg.At(i, j)
- want := color.RGBAModel.Convert(img.At(i+1, j+1))
- if got != want {
- t.Errorf("img0 At(%d, %d): got %v; want %v", i, j, got, want)
- }
- }
- }
- }
- type mutableRGBA struct {
- r, g, b, a uint8
- }
- func (c *mutableRGBA) RGBA() (r, g, b, a uint32) {
- return uint32(c.r) * 0x101, uint32(c.g) * 0x101, uint32(c.b) * 0x101, uint32(c.a) * 0x101
- }
- func TestImageFill(t *testing.T) {
- w, h := 10, 10
- img := ebiten.NewImage(w, h)
- clr := &mutableRGBA{0x80, 0x80, 0x80, 0x80}
- img.Fill(clr)
- clr.r = 0
- for j := 0; j < h; j++ {
- for i := 0; i < w; i++ {
- got := img.At(i, j)
- want := color.RGBA{R: 0x80, G: 0x80, B: 0x80, A: 0x80}
- if got != want {
- t.Errorf("img At(%d, %d): got %v; want %v", i, j, got, want)
- }
- }
- }
- }
- // Issue #740
- func TestImageClear(t *testing.T) {
- const w, h = 128, 256
- img := ebiten.NewImage(w, h)
- img.Fill(color.White)
- for j := 0; j < h; j++ {
- for i := 0; i < w; i++ {
- got := img.At(i, j)
- want := color.RGBA{R: 0xff, G: 0xff, B: 0xff, A: 0xff}
- if got != want {
- t.Errorf("img At(%d, %d): got %v; want %v", i, j, got, want)
- }
- }
- }
- img.Clear()
- for j := 0; j < h; j++ {
- for i := 0; i < w; i++ {
- got := img.At(i, j)
- want := color.RGBA{}
- if got != want {
- t.Errorf("img At(%d, %d): got %v; want %v", i, j, got, want)
- }
- }
- }
- }
- // Issue #317, #558, #724
- func TestImageEdge(t *testing.T) {
- // TODO: This test is not so meaningful after #1218. Do we remove this?
- if skipTooSlowTests(t) {
- return
- }
- const (
- img0Width = 10
- img0Height = 10
- img0InnerWidth = 10
- img0InnerHeight = 10
- img1Width = 32
- img1Height = 32
- )
- img0 := ebiten.NewImage(img0Width, img0Height)
- pixels := make([]uint8, 4*img0Width*img0Height)
- for j := 0; j < img0Height; j++ {
- for i := 0; i < img0Width; i++ {
- idx := 4 * (i + j*img0Width)
- pixels[idx] = 0xff
- pixels[idx+1] = 0
- pixels[idx+2] = 0
- pixels[idx+3] = 0xff
- }
- }
- img0.WritePixels(pixels)
- img1 := ebiten.NewImage(img1Width, img1Height)
- red := color.RGBA{R: 0xff, A: 0xff}
- transparent := color.RGBA{}
- angles := []float64{}
- for a := 0; a < 1440; a++ {
- angles = append(angles, float64(a)/1440*2*math.Pi)
- }
- for a := 0; a < 4096; a += 3 {
- // a++ should be fine, but it takes long to test.
- angles = append(angles, float64(a)/4096*2*math.Pi)
- }
- for _, s := range []float64{1, 0.5, 0.25} {
- for _, f := range []ebiten.Filter{ebiten.FilterNearest, ebiten.FilterLinear} {
- for _, a := range angles {
- for _, testDrawTriangles := range []bool{false, true} {
- img1.Clear()
- w, h := img0.Bounds().Dx(), img0.Bounds().Dy()
- b := img0.Bounds()
- var geo ebiten.GeoM
- geo.Translate(-float64(w)/2, -float64(h)/2)
- geo.Scale(s, s)
- geo.Rotate(a)
- geo.Translate(img1Width/2, img1Height/2)
- if !testDrawTriangles {
- op := &ebiten.DrawImageOptions{}
- op.GeoM = geo
- op.Filter = f
- img1.DrawImage(img0, op)
- } else {
- op := &ebiten.DrawTrianglesOptions{}
- dx0, dy0 := geo.Apply(0, 0)
- dx1, dy1 := geo.Apply(float64(w), 0)
- dx2, dy2 := geo.Apply(0, float64(h))
- dx3, dy3 := geo.Apply(float64(w), float64(h))
- vs := []ebiten.Vertex{
- {
- DstX: float32(dx0),
- DstY: float32(dy0),
- SrcX: float32(b.Min.X),
- SrcY: float32(b.Min.Y),
- ColorR: 1,
- ColorG: 1,
- ColorB: 1,
- ColorA: 1,
- },
- {
- DstX: float32(dx1),
- DstY: float32(dy1),
- SrcX: float32(b.Max.X),
- SrcY: float32(b.Min.Y),
- ColorR: 1,
- ColorG: 1,
- ColorB: 1,
- ColorA: 1,
- },
- {
- DstX: float32(dx2),
- DstY: float32(dy2),
- SrcX: float32(b.Min.X),
- SrcY: float32(b.Max.Y),
- ColorR: 1,
- ColorG: 1,
- ColorB: 1,
- ColorA: 1,
- },
- {
- DstX: float32(dx3),
- DstY: float32(dy3),
- SrcX: float32(b.Max.X),
- SrcY: float32(b.Max.Y),
- ColorR: 1,
- ColorG: 1,
- ColorB: 1,
- ColorA: 1,
- },
- }
- is := []uint16{0, 1, 2, 1, 2, 3}
- op.Filter = f
- img1.DrawTriangles(vs, is, img0, op)
- }
- allTransparent := true
- for j := 0; j < img1Height; j++ {
- for i := 0; i < img1Width; i++ {
- c := img1.At(i, j)
- if c == transparent {
- continue
- }
- allTransparent = false
- switch f {
- case ebiten.FilterNearest:
- if c == red {
- continue
- }
- case ebiten.FilterLinear:
- if _, g, b, _ := c.RGBA(); g == 0 && b == 0 {
- continue
- }
- }
- t.Fatalf("img1.At(%d, %d) (filter: %d, scale: %f, angle: %f, draw-triangles?: %t) want: red or transparent, got: %v", i, j, f, s, a, testDrawTriangles, c)
- }
- }
- if allTransparent {
- t.Fatalf("img1 (filter: %d, scale: %f, angle: %f, draw-triangles?: %t) is transparent but should not", f, s, a, testDrawTriangles)
- }
- }
- }
- }
- }
- }
- // Issue #419
- func TestImageTooManyFill(t *testing.T) {
- const width = 1024
- indexToColor := func(index int) uint8 {
- return uint8((17*index + 0x40) % 256)
- }
- src := ebiten.NewImage(1, 1)
- dst := ebiten.NewImage(width, 1)
- for i := 0; i < width; i++ {
- c := indexToColor(i)
- src.Fill(color.RGBA{R: c, G: c, B: c, A: 0xff})
- op := &ebiten.DrawImageOptions{}
- op.GeoM.Translate(float64(i), 0)
- dst.DrawImage(src, op)
- }
- for i := 0; i < width; i++ {
- c := indexToColor(i)
- got := dst.At(i, 0).(color.RGBA)
- want := color.RGBA{R: c, G: c, B: c, A: 0xff}
- if !sameColors(got, want, 1) {
- t.Errorf("dst.At(%d, %d): got %v, want: %v", i, 0, got, want)
- }
- }
- }
- func BenchmarkDrawImage(b *testing.B) {
- img0 := ebiten.NewImage(16, 16)
- img1 := ebiten.NewImage(16, 16)
- op := &ebiten.DrawImageOptions{}
- for i := 0; i < b.N; i++ {
- img0.DrawImage(img1, op)
- }
- }
- func BenchmarkDrawTriangles(b *testing.B) {
- const w, h = 16, 16
- img0 := ebiten.NewImage(w, h)
- img1 := ebiten.NewImage(w, h)
- op := &ebiten.DrawTrianglesOptions{}
- vs := []ebiten.Vertex{
- {
- DstX: 0,
- DstY: 0,
- SrcX: 0,
- SrcY: 0,
- ColorR: 1,
- ColorG: 1,
- ColorB: 1,
- ColorA: 1,
- },
- {
- DstX: w,
- DstY: 0,
- SrcX: w,
- SrcY: 0,
- ColorR: 1,
- ColorG: 1,
- ColorB: 1,
- ColorA: 1,
- },
- {
- DstX: 0,
- DstY: h,
- SrcX: 0,
- SrcY: h,
- ColorR: 1,
- ColorG: 1,
- ColorB: 1,
- ColorA: 1,
- },
- {
- DstX: w,
- DstY: h,
- SrcX: w,
- SrcY: h,
- ColorR: 1,
- ColorG: 1,
- ColorB: 1,
- ColorA: 1,
- },
- }
- is := []uint16{0, 1, 2, 1, 2, 3}
- for i := 0; i < b.N; i++ {
- img0.DrawTriangles(vs, is, img1, op)
- }
- }
- func TestImageLinearGraduation(t *testing.T) {
- img0 := ebiten.NewImage(2, 2)
- img0.WritePixels([]byte{
- 0xff, 0x00, 0x00, 0xff,
- 0x00, 0xff, 0x00, 0xff,
- 0x00, 0x00, 0xff, 0xff,
- 0xff, 0xff, 0xff, 0xff,
- })
- const w, h = 32, 32
- img1 := ebiten.NewImage(w, h)
- op := &ebiten.DrawImageOptions{}
- op.GeoM.Scale(w, h)
- op.GeoM.Translate(-w/4, -h/4)
- op.Filter = ebiten.FilterLinear
- img1.DrawImage(img0, op)
- for j := 1; j < h-1; j++ {
- for i := 1; i < w-1; i++ {
- c := img1.At(i, j).(color.RGBA)
- if c.R == 0 || c.R == 0xff {
- t.Errorf("img1.At(%d, %d).R must be in between 0x01 and 0xfe but %v", i, j, c)
- }
- }
- }
- }
- func TestImageOutside(t *testing.T) {
- src := ebiten.NewImage(5, 10) // internal texture size is 8x16.
- dst := ebiten.NewImage(4, 4)
- src.Fill(color.RGBA{R: 0xff, A: 0xff})
- cases := []struct {
- X, Y, Width, Height int
- }{
- {-4, -4, 4, 4},
- {5, 0, 4, 4},
- {0, 10, 4, 4},
- {5, 10, 4, 4},
- {8, 0, 4, 4},
- {0, 16, 4, 4},
- {8, 16, 4, 4},
- {8, -4, 4, 4},
- {-4, 16, 4, 4},
- {5, 10, 0, 0},
- {5, 10, -2, -2}, // non-well-formed rectangle
- }
- for _, c := range cases {
- dst.Clear()
- op := &ebiten.DrawImageOptions{}
- op.GeoM.Translate(0, 0)
- dst.DrawImage(src.SubImage(image.Rectangle{
- Min: image.Pt(c.X, c.Y),
- Max: image.Pt(c.X+c.Width, c.Y+c.Height),
- }).(*ebiten.Image), op)
- for j := 0; j < 4; j++ {
- for i := 0; i < 4; i++ {
- got := dst.At(i, j).(color.RGBA)
- want := color.RGBA{}
- if got != want {
- t.Errorf("src(x: %d, y: %d, w: %d, h: %d), dst At(%d, %d): got %v, want: %v", c.X, c.Y, c.Width, c.Height, i, j, got, want)
- }
- }
- }
- }
- }
- func TestImageOutsideUpperLeft(t *testing.T) {
- src := ebiten.NewImage(4, 4)
- dst1 := ebiten.NewImage(16, 16)
- dst2 := ebiten.NewImage(16, 16)
- src.Fill(color.RGBA{R: 0xff, A: 0xff})
- op := &ebiten.DrawImageOptions{}
- op.GeoM.Rotate(math.Pi / 4)
- dst1.DrawImage(src.SubImage(image.Rect(-4, -4, 8, 8)).(*ebiten.Image), op)
- op = &ebiten.DrawImageOptions{}
- op.GeoM.Rotate(math.Pi / 4)
- dst2.DrawImage(src, op)
- for j := 0; j < 16; j++ {
- for i := 0; i < 16; i++ {
- got := dst1.At(i, j).(color.RGBA)
- want := dst2.At(i, j).(color.RGBA)
- if got != want {
- t.Errorf("got: dst1.At(%d, %d): %v, want: dst2.At(%d, %d): %v", i, j, got, i, j, want)
- }
- }
- }
- }
- func TestImageSize(t *testing.T) {
- const (
- w = 17
- h = 31
- )
- img := ebiten.NewImage(w, h)
- gotW, gotH := img.Bounds().Dx(), img.Bounds().Dy()
- if gotW != w {
- t.Errorf("got: %d, want: %d", gotW, w)
- }
- if gotH != h {
- t.Errorf("got: %d, want: %d", gotH, h)
- }
- }
- func TestImageSize1(t *testing.T) {
- src := ebiten.NewImage(1, 1)
- dst := ebiten.NewImage(1, 1)
- src.Fill(color.White)
- dst.DrawImage(src, nil)
- got := src.At(0, 0).(color.RGBA)
- want := color.RGBA{R: 0xff, G: 0xff, B: 0xff, A: 0xff}
- if !sameColors(got, want, 1) {
- t.Errorf("got: %v, want: %v", got, want)
- }
- }
- // TODO: Enable this test again. This test fails after #1217 is fixed.
- func Skip_TestImageSize4096(t *testing.T) {
- src := ebiten.NewImage(4096, 4096)
- dst := ebiten.NewImage(4096, 4096)
- pix := make([]byte, 4096*4096*4)
- for i := 0; i < 4096; i++ {
- j := 4095
- idx := 4 * (i + j*4096)
- pix[idx] = uint8(i + j)
- pix[idx+1] = uint8((i + j) >> 8)
- pix[idx+2] = uint8((i + j) >> 16)
- pix[idx+3] = 0xff
- }
- for j := 0; j < 4096; j++ {
- i := 4095
- idx := 4 * (i + j*4096)
- pix[idx] = uint8(i + j)
- pix[idx+1] = uint8((i + j) >> 8)
- pix[idx+2] = uint8((i + j) >> 16)
- pix[idx+3] = 0xff
- }
- src.WritePixels(pix)
- dst.DrawImage(src, nil)
- for i := 4095; i < 4096; i++ {
- j := 4095
- got := dst.At(i, j).(color.RGBA)
- want := color.RGBA{R: uint8(i + j), G: uint8((i + j) >> 8), B: uint8((i + j) >> 16), A: 0xff}
- if got != want {
- t.Errorf("At(%d, %d): got: %v, want: %v", i, j, got, want)
- }
- }
- for j := 4095; j < 4096; j++ {
- i := 4095
- got := dst.At(i, j).(color.RGBA)
- want := color.RGBA{R: uint8(i + j), G: uint8((i + j) >> 8), B: uint8((i + j) >> 16), A: 0xff}
- if got != want {
- t.Errorf("At(%d, %d): got: %v, want: %v", i, j, got, want)
- }
- }
- }
- func TestImageCopy(t *testing.T) {
- defer func() {
- if r := recover(); r == nil {
- t.Errorf("copying image and using it must panic")
- }
- }()
- img0 := ebiten.NewImage(256, 256)
- img1 := *img0
- img1.Fill(color.Transparent)
- }
- // Issue #611, #907
- func TestImageStretch(t *testing.T) {
- if skipTooSlowTests(t) {
- return
- }
- const w = 16
- dst := ebiten.NewImage(w, maxImageSize)
- loop:
- for h := 1; h <= 32; h++ {
- src := ebiten.NewImage(w+2, h+2)
- pix := make([]byte, 4*(w+2)*(h+2))
- for i := 0; i < (w+2)*(h+2); i++ {
- pix[4*i] = 0xff
- pix[4*i+3] = 0xff
- }
- src.WritePixels(pix)
- dh := dst.Bounds().Dy()
- for i := 0; i < dh; {
- dst.Clear()
- op := &ebiten.DrawImageOptions{}
- op.GeoM.Scale(1, float64(i)/float64(h))
- dst.DrawImage(src.SubImage(image.Rect(1, 1, w+1, h+1)).(*ebiten.Image), op)
- for j := -1; j <= 1; j++ {
- if i+j < 0 {
- continue
- }
- got := dst.At(0, i+j).(color.RGBA)
- want := color.RGBA{}
- if j < 0 {
- want = color.RGBA{R: 0xff, A: 0xff}
- }
- if got != want {
- t.Errorf("At(%d, %d) (height=%d, scale=%d/%d): got: %v, want: %v", 0, i+j, h, i, h, got, want)
- continue loop
- }
- }
- switch i % 32 {
- case 31, 0:
- i++
- case 1:
- i += 32 - 2
- default:
- panic("not reached")
- }
- }
- }
- }
- func TestImageSprites(t *testing.T) {
- const (
- width = 512
- height = 512
- )
- src := ebiten.NewImage(4, 4)
- src.Fill(color.RGBA{R: 0xff, G: 0xff, B: 0xff, A: 0xff})
- dst := ebiten.NewImage(width, height)
- for j := 0; j < height/4; j++ {
- for i := 0; i < width/4; i++ {
- op := &ebiten.DrawImageOptions{}
- op.GeoM.Translate(float64(i*4), float64(j*4))
- dst.DrawImage(src, op)
- }
- }
- for j := 0; j < height/4; j++ {
- for i := 0; i < width/4; i++ {
- got := dst.At(i*4, j*4).(color.RGBA)
- want := color.RGBA{R: 0xff, G: 0xff, B: 0xff, A: 0xff}
- if !sameColors(got, want, 1) {
- t.Errorf("dst.At(%d, %d): got %v, want: %v", i*4, j*4, got, want)
- }
- }
- }
- }
- // Disabled: it does not make sense to expect deterministic mipmap results (#909).
- func Disabled_TestImageMipmap(t *testing.T) {
- src, _, err := openEbitenImage()
- if err != nil {
- t.Fatal(err)
- return
- }
- w, h := src.Bounds().Dx(), src.Bounds().Dy()
- l1 := ebiten.NewImage(w/2, h/2)
- op := &ebiten.DrawImageOptions{}
- op.GeoM.Scale(1/2.0, 1/2.0)
- op.Filter = ebiten.FilterLinear
- l1.DrawImage(src, op)
- l1w, l1h := l1.Bounds().Dx(), l1.Bounds().Dy()
- l2 := ebiten.NewImage(l1w/2, l1h/2)
- op = &ebiten.DrawImageOptions{}
- op.GeoM.Scale(1/2.0, 1/2.0)
- op.Filter = ebiten.FilterLinear
- l2.DrawImage(l1, op)
- gotDst := ebiten.NewImage(w, h)
- op = &ebiten.DrawImageOptions{}
- op.GeoM.Scale(1/5.0, 1/5.0)
- op.Filter = ebiten.FilterLinear
- gotDst.DrawImage(src, op)
- wantDst := ebiten.NewImage(w, h)
- op = &ebiten.DrawImageOptions{}
- op.GeoM.Scale(4.0/5.0, 4.0/5.0)
- op.Filter = ebiten.FilterLinear
- wantDst.DrawImage(l2, op)
- for j := 0; j < h; j++ {
- for i := 0; i < w; i++ {
- got := gotDst.At(i, j).(color.RGBA)
- want := wantDst.At(i, j).(color.RGBA)
- if !sameColors(got, want, 1) {
- t.Errorf("At(%d, %d): got: %v, want: %v", i, j, got, want)
- }
- }
- }
- }
- // Disabled: it does not make sense to expect deterministic mipmap results (#909).
- func Disabled_TestImageMipmapNegativeDet(t *testing.T) {
- src, _, err := openEbitenImage()
- if err != nil {
- t.Fatal(err)
- return
- }
- w, h := src.Bounds().Dx(), src.Bounds().Dy()
- l1 := ebiten.NewImage(w/2, h/2)
- op := &ebiten.DrawImageOptions{}
- op.GeoM.Scale(1/2.0, 1/2.0)
- op.Filter = ebiten.FilterLinear
- l1.DrawImage(src, op)
- l1w, l1h := l1.Bounds().Dx(), l1.Bounds().Dy()
- l2 := ebiten.NewImage(l1w/2, l1h/2)
- op = &ebiten.DrawImageOptions{}
- op.GeoM.Scale(1/2.0, 1/2.0)
- op.Filter = ebiten.FilterLinear
- l2.DrawImage(l1, op)
- gotDst := ebiten.NewImage(w, h)
- op = &ebiten.DrawImageOptions{}
- op.GeoM.Scale(-1/5.0, -1/5.0)
- op.GeoM.Translate(float64(w), float64(h))
- op.Filter = ebiten.FilterLinear
- gotDst.DrawImage(src, op)
- wantDst := ebiten.NewImage(w, h)
- op = &ebiten.DrawImageOptions{}
- op.GeoM.Scale(-4.0/5.0, -4.0/5.0)
- op.GeoM.Translate(float64(w), float64(h))
- op.Filter = ebiten.FilterLinear
- wantDst.DrawImage(l2, op)
- allZero := true
- for j := 0; j < h; j++ {
- for i := 0; i < w; i++ {
- got := gotDst.At(i, j).(color.RGBA)
- want := wantDst.At(i, j).(color.RGBA)
- if !sameColors(got, want, 1) {
- t.Errorf("At(%d, %d): got: %v, want: %v", i, j, got, want)
- }
- if got.A > 0 {
- allZero = false
- }
- }
- }
- if allZero {
- t.Errorf("the image must include non-zero values but not")
- }
- }
- // Issue #710
- func TestImageMipmapColor(t *testing.T) {
- img0 := ebiten.NewImage(256, 256)
- img1 := ebiten.NewImage(128, 128)
- img1.Fill(color.White)
- for i := 0; i < 8; i++ {
- img0.Clear()
- s := 1 - float64(i)/8
- op := &ebiten.DrawImageOptions{}
- op.Filter = ebiten.FilterLinear
- op.GeoM.Scale(s, s)
- op.ColorScale.Scale(1, 1, 0, 1)
- img0.DrawImage(img1, op)
- op.GeoM.Translate(128, 0)
- op.ColorScale.Reset()
- op.ColorScale.Scale(0, 1, 1, 1)
- img0.DrawImage(img1, op)
- want := color.RGBA{G: 0xff, B: 0xff, A: 0xff}
- got := img0.At(128, 0)
- if got != want {
- t.Errorf("want: %v, got: %v", want, got)
- }
- }
- }
- // Issue #725
- func TestImageMiamapAndDrawTriangle(t *testing.T) {
- img0 := ebiten.NewImage(32, 32)
- img1 := ebiten.NewImage(128, 128)
- img2 := ebiten.NewImage(128, 128)
- // Fill img1 red and create img1's mipmap
- img1.Fill(color.RGBA{R: 0xff, A: 0xff})
- op := &ebiten.DrawImageOptions{}
- op.GeoM.Scale(0.25, 0.25)
- op.Filter = ebiten.FilterLinear
- img0.DrawImage(img1, op)
- // Call DrawTriangle on img1 and fill it with green
- img2.Fill(color.RGBA{G: 0xff, A: 0xff})
- vs := []ebiten.Vertex{
- {
- DstX: 0,
- DstY: 0,
- SrcX: 0,
- SrcY: 0,
- ColorR: 1,
- ColorG: 1,
- ColorB: 1,
- ColorA: 1,
- },
- {
- DstX: 128,
- DstY: 0,
- SrcX: 128,
- SrcY: 0,
- ColorR: 1,
- ColorG: 1,
- ColorB: 1,
- ColorA: 1,
- },
- {
- DstX: 0,
- DstY: 128,
- SrcX: 0,
- SrcY: 128,
- ColorR: 1,
- ColorG: 1,
- ColorB: 1,
- ColorA: 1,
- },
- {
- DstX: 128,
- DstY: 128,
- SrcX: 128,
- SrcY: 128,
- ColorR: 1,
- ColorG: 1,
- ColorB: 1,
- ColorA: 1,
- },
- }
- img1.DrawTriangles(vs, []uint16{0, 1, 2, 1, 2, 3}, img2, nil)
- // Draw img1 (green) again. Confirm mipmap is correctly updated.
- img0.Clear()
- op = &ebiten.DrawImageOptions{}
- op.GeoM.Scale(0.25, 0.25)
- op.Filter = ebiten.FilterLinear
- img0.DrawImage(img1, op)
- w, h := img0.Bounds().Dx(), img0.Bounds().Dy()
- for j := 0; j < h; j++ {
- for i := 0; i < w; i++ {
- c := img0.At(i, j).(color.RGBA)
- if c.R != 0 {
- t.Errorf("img0.At(%d, %d): red want %d got %d", i, j, 0, c.R)
- }
- }
- }
- }
- func TestImageSubImageAt(t *testing.T) {
- img := ebiten.NewImage(16, 16)
- img.Fill(color.RGBA{R: 0xff, A: 0xff})
- got := img.SubImage(image.Rect(1, 1, 16, 16)).At(0, 0).(color.RGBA)
- want := color.RGBA{}
- if got != want {
- t.Errorf("got: %v, want: %v", got, want)
- }
- got = img.SubImage(image.Rect(1, 1, 16, 16)).At(1, 1).(color.RGBA)
- want = color.RGBA{R: 0xff, A: 0xff}
- if got != want {
- t.Errorf("got: %v, want: %v", got, want)
- }
- }
- func TestImageSubImageSize(t *testing.T) {
- img := ebiten.NewImage(16, 16)
- img.Fill(color.RGBA{R: 0xff, A: 0xff})
- got := img.SubImage(image.Rect(1, 1, 16, 16)).Bounds().Dx()
- want := 15
- if got != want {
- t.Errorf("got: %v, want: %v", got, want)
- }
- }
- func TestImageDrawImmediately(t *testing.T) {
- const w, h = 16, 16
- img0 := ebiten.NewImage(w, h)
- img1 := ebiten.NewImage(w, h)
- // Do not manipulate img0 here.
- img0.Fill(color.RGBA{R: 0xff, A: 0xff})
- for j := 0; j < h; j++ {
- for i := 0; i < w; i++ {
- got := img0.At(i, j).(color.RGBA)
- want := color.RGBA{R: 0xff, A: 0xff}
- if got != want {
- t.Errorf("img0.At(%d, %d): got %v, want: %v", i, j, got, want)
- }
- }
- }
- img0.DrawImage(img1, nil)
- for j := 0; j < h; j++ {
- for i := 0; i < w; i++ {
- got := img0.At(i, j).(color.RGBA)
- want := color.RGBA{R: 0xff, A: 0xff}
- if got != want {
- t.Errorf("img0.At(%d, %d): got %v, want: %v", i, j, got, want)
- }
- }
- }
- }
- // Issue #669, #759
- func TestImageLinearFilterGlitch(t *testing.T) {
- const w, h = 200, 12
- const scale = 1.2
- src := ebiten.NewImage(w, h)
- dst := ebiten.NewImage(int(math.Floor(w*scale)), h)
- pix := make([]byte, 4*w*h)
- for j := 0; j < h; j++ {
- for i := 0; i < w; i++ {
- idx := i + w*j
- if j < 3 {
- pix[4*idx] = 0xff
- pix[4*idx+1] = 0xff
- pix[4*idx+2] = 0xff
- pix[4*idx+3] = 0xff
- } else {
- pix[4*idx] = 0
- pix[4*idx+1] = 0
- pix[4*idx+2] = 0
- pix[4*idx+3] = 0xff
- }
- }
- }
- src.WritePixels(pix)
- for _, f := range []ebiten.Filter{ebiten.FilterNearest, ebiten.FilterLinear} {
- op := &ebiten.DrawImageOptions{}
- op.GeoM.Scale(scale, 1)
- op.Filter = f
- dst.DrawImage(src, op)
- for j := 1; j < h-1; j++ {
- offset := int(math.Ceil(scale))
- for i := offset; i < int(math.Floor(w*scale))-offset; i++ {
- got := dst.At(i, j).(color.RGBA)
- var want color.RGBA
- if j < 3 {
- want = color.RGBA{R: 0xff, G: 0xff, B: 0xff, A: 0xff}
- } else {
- want = color.RGBA{A: 0xff}
- }
- if got != want {
- t.Errorf("dst.At(%d, %d): filter: %d, got: %v, want: %v", i, j, f, got, want)
- }
- }
- }
- }
- }
- // Issue #1212
- func TestImageLinearFilterGlitch2(t *testing.T) {
- const w, h = 100, 100
- src := ebiten.NewImage(w, h)
- dst := ebiten.NewImage(w, h)
- idx := 0
- pix := make([]byte, 4*w*h)
- for j := 0; j < h; j++ {
- for i := 0; i < w; i++ {
- if i+j < 100 {
- pix[4*idx] = 0
- pix[4*idx+1] = 0
- pix[4*idx+2] = 0
- pix[4*idx+3] = 0xff
- } else {
- pix[4*idx] = 0xff
- pix[4*idx+1] = 0xff
- pix[4*idx+2] = 0xff
- pix[4*idx+3] = 0xff
- }
- idx++
- }
- }
- src.WritePixels(pix)
- op := &ebiten.DrawImageOptions{}
- op.Filter = ebiten.FilterLinear
- dst.DrawImage(src, op)
- for j := 0; j < h; j++ {
- for i := 0; i < w; i++ {
- got := dst.At(i, j).(color.RGBA)
- var want color.RGBA
- if i+j < 100 {
- want = color.RGBA{A: 0xff}
- } else {
- want = color.RGBA{R: 0xff, G: 0xff, B: 0xff, A: 0xff}
- }
- if !sameColors(got, want, 1) {
- t.Errorf("dst.At(%d, %d): got: %v, want: %v", i, j, got, want)
- }
- }
- }
- }
- func TestImageAddressRepeat(t *testing.T) {
- const w, h = 16, 16
- src := ebiten.NewImage(w, h)
- dst := ebiten.NewImage(w, h)
- pix := make([]byte, 4*w*h)
- for j := 0; j < h; j++ {
- for i := 0; i < w; i++ {
- idx := 4 * (i + j*w)
- if 4 <= i && i < 8 && 4 <= j && j < 8 {
- pix[idx] = byte(i-4) * 0x10
- pix[idx+1] = byte(j-4) * 0x10
- pix[idx+2] = 0
- pix[idx+3] = 0xff
- } else {
- pix[idx] = 0
- pix[idx+1] = 0
- pix[idx+2] = 0xff
- pix[idx+3] = 0xff
- }
- }
- }
- src.WritePixels(pix)
- vs := []ebiten.Vertex{
- {
- DstX: 0,
- DstY: 0,
- SrcX: 0,
- SrcY: 0,
- ColorR: 1,
- ColorG: 1,
- ColorB: 1,
- ColorA: 1,
- },
- {
- DstX: w,
- DstY: 0,
- SrcX: w,
- SrcY: 0,
- ColorR: 1,
- ColorG: 1,
- ColorB: 1,
- ColorA: 1,
- },
- {
- DstX: 0,
- DstY: h,
- SrcX: 0,
- SrcY: h,
- ColorR: 1,
- ColorG: 1,
- ColorB: 1,
- ColorA: 1,
- },
- {
- DstX: w,
- DstY: h,
- SrcX: w,
- SrcY: h,
- ColorR: 1,
- ColorG: 1,
- ColorB: 1,
- ColorA: 1,
- },
- }
- is := []uint16{0, 1, 2, 1, 2, 3}
- op := &ebiten.DrawTrianglesOptions{}
- op.Address = ebiten.AddressRepeat
- dst.DrawTriangles(vs, is, src.SubImage(image.Rect(4, 4, 8, 8)).(*ebiten.Image), op)
- for j := 0; j < h; j++ {
- for i := 0; i < w; i++ {
- got := dst.At(i, j).(color.RGBA)
- want := color.RGBA{R: byte(i%4) * 0x10, G: byte(j%4) * 0x10, A: 0xff}
- if !sameColors(got, want, 1) {
- t.Errorf("dst.At(%d, %d): got %v, want: %v", i, j, got, want)
- }
- }
- }
- }
- func TestImageAddressRepeatNegativePosition(t *testing.T) {
- const w, h = 16, 16
- src := ebiten.NewImage(w, h)
- dst := ebiten.NewImage(w, h)
- pix := make([]byte, 4*w*h)
- for j := 0; j < h; j++ {
- for i := 0; i < w; i++ {
- idx := 4 * (i + j*w)
- if 4 <= i && i < 8 && 4 <= j && j < 8 {
- pix[idx] = byte(i-4) * 0x10
- pix[idx+1] = byte(j-4) * 0x10
- pix[idx+2] = 0
- pix[idx+3] = 0xff
- } else {
- pix[idx] = 0
- pix[idx+1] = 0
- pix[idx+2] = 0xff
- pix[idx+3] = 0xff
- }
- }
- }
- src.WritePixels(pix)
- vs := []ebiten.Vertex{
- {
- DstX: 0,
- DstY: 0,
- SrcX: -w,
- SrcY: -h,
- ColorR: 1,
- ColorG: 1,
- ColorB: 1,
- ColorA: 1,
- },
- {
- DstX: w,
- DstY: 0,
- SrcX: 0,
- SrcY: -h,
- ColorR: 1,
- ColorG: 1,
- ColorB: 1,
- ColorA: 1,
- },
- {
- DstX: 0,
- DstY: h,
- SrcX: -w,
- SrcY: 0,
- ColorR: 1,
- ColorG: 1,
- ColorB: 1,
- ColorA: 1,
- },
- {
- DstX: w,
- DstY: h,
- SrcX: 0,
- SrcY: 0,
- ColorR: 1,
- ColorG: 1,
- ColorB: 1,
- ColorA: 1,
- },
- }
- is := []uint16{0, 1, 2, 1, 2, 3}
- op := &ebiten.DrawTrianglesOptions{}
- op.Address = ebiten.AddressRepeat
- dst.DrawTriangles(vs, is, src.SubImage(image.Rect(4, 4, 8, 8)).(*ebiten.Image), op)
- for j := 0; j < h; j++ {
- for i := 0; i < w; i++ {
- got := dst.At(i, j).(color.RGBA)
- want := color.RGBA{R: byte(i%4) * 0x10, G: byte(j%4) * 0x10, A: 0xff}
- if !sameColors(got, want, 1) {
- t.Errorf("dst.At(%d, %d): got %v, want: %v", i, j, got, want)
- }
- }
- }
- }
- func TestImageWritePixelsAfterClear(t *testing.T) {
- const w, h = 256, 256
- img := ebiten.NewImage(w, h)
- img.WritePixels(make([]byte, 4*w*h))
- // Clear used to call DrawImage to clear the image, which was the cause of crash. It is because after
- // DrawImage is called, WritePixels for a region is forbidden.
- //
- // Now WritePixels was always called at Clear instead.
- img.Clear()
- img.WritePixels(make([]byte, 4*w*h))
- // The test passes if this doesn't crash.
- }
- func TestImageSet(t *testing.T) {
- type Pt struct {
- X, Y int
- }
- const w, h = 16, 16
- img := ebiten.NewImage(w, h)
- colors := map[Pt]color.RGBA{
- {1, 2}: {3, 4, 5, 6},
- {7, 8}: {9, 10, 11, 12},
- {13, 14}: {15, 16, 17, 18},
- {-1, -1}: {19, 20, 21, 22},
- }
- for p, c := range colors {
- img.Set(p.X, p.Y, c)
- }
- for j := 0; j < h; j++ {
- for i := 0; i < w; i++ {
- got := img.At(i, j).(color.RGBA)
- var want color.RGBA
- if c, ok := colors[Pt{i, j}]; ok {
- want = c
- }
- if got != want {
- t.Errorf("img.At(%d, %d): got: %v, want: %v", i, j, got, want)
- }
- }
- }
- }
- func TestImageSetAndDraw(t *testing.T) {
- type Pt struct {
- X, Y int
- }
- const w, h = 16, 16
- src := ebiten.NewImage(w, h)
- dst := ebiten.NewImage(w, h)
- colors := map[Pt]color.RGBA{
- {1, 2}: {3, 4, 5, 6},
- {7, 8}: {9, 10, 11, 12},
- {13, 14}: {15, 16, 17, 18},
- }
- for p, c := range colors {
- src.Set(p.X, p.Y, c)
- dst.Set(p.X+1, p.Y+1, c)
- }
- dst.DrawImage(src, nil)
- for j := 0; j < h; j++ {
- for i := 0; i < w; i++ {
- got := dst.At(i, j).(color.RGBA)
- var want color.RGBA
- if c, ok := colors[Pt{i, j}]; ok {
- want = c
- }
- if c, ok := colors[Pt{i - 1, j - 1}]; ok {
- want = c
- }
- if got != want {
- t.Errorf("img.At(%d, %d): got: %v, want: %v", i, j, got, want)
- }
- }
- }
- src.Clear()
- dst.Clear()
- for p, c := range colors {
- src.Set(p.X, p.Y, c)
- dst.Set(p.X+1, p.Y+1, c)
- }
- op := &ebiten.DrawImageOptions{}
- op.GeoM.Translate(2, 2)
- dst.DrawImage(src.SubImage(image.Rect(2, 2, w-2, h-2)).(*ebiten.Image), op)
- for j := 0; j < h; j++ {
- for i := 0; i < w; i++ {
- got := dst.At(i, j).(color.RGBA)
- var want color.RGBA
- if 2 <= i && 2 <= j && i < w-2 && j < h-2 {
- if c, ok := colors[Pt{i, j}]; ok {
- want = c
- }
- }
- if c, ok := colors[Pt{i - 1, j - 1}]; ok {
- want = c
- }
- if got != want {
- t.Errorf("img.At(%d, %d): got: %v, want: %v", i, j, got, want)
- }
- }
- }
- }
- func TestImageAlphaOnBlack(t *testing.T) {
- const w, h = 16, 16
- src0 := ebiten.NewImage(w, h)
- src1 := ebiten.NewImage(w, h)
- dst0 := ebiten.NewImage(w, h)
- dst1 := ebiten.NewImage(w, h)
- pix0 := make([]byte, 4*w*h)
- for j := 0; j < h; j++ {
- for i := 0; i < w; i++ {
- if (i/3)%2 == (j/3)%2 {
- pix0[4*(i+j*w)] = 0xff
- pix0[4*(i+j*w)+1] = 0xff
- pix0[4*(i+j*w)+2] = 0xff
- pix0[4*(i+j*w)+3] = 0xff
- }
- }
- }
- src0.WritePixels(pix0)
- pix1 := make([]byte, 4*w*h)
- for j := 0; j < h; j++ {
- for i := 0; i < w; i++ {
- if (i/3)%2 == (j/3)%2 {
- pix1[4*(i+j*w)] = 0xff
- pix1[4*(i+j*w)+1] = 0xff
- pix1[4*(i+j*w)+2] = 0xff
- pix1[4*(i+j*w)+3] = 0xff
- } else {
- pix1[4*(i+j*w)] = 0
- pix1[4*(i+j*w)+1] = 0
- pix1[4*(i+j*w)+2] = 0
- pix1[4*(i+j*w)+3] = 0xff
- }
- }
- }
- src1.WritePixels(pix1)
- dst0.Fill(color.Black)
- dst1.Fill(color.Black)
- op := &ebiten.DrawImageOptions{}
- op.GeoM.Scale(0.5, 0.5)
- op.Filter = ebiten.FilterLinear
- dst0.DrawImage(src0, op)
- dst1.DrawImage(src1, op)
- gray := false
- for j := 0; j < h; j++ {
- for i := 0; i < w; i++ {
- got := dst0.At(i, j)
- want := dst1.At(i, j)
- if got != want {
- t.Errorf("At(%d, %d): got: %v, want: %v", i, j, got, want)
- }
- if r := got.(color.RGBA).R; 0 < r && r < 255 {
- gray = true
- }
- }
- }
- if !gray {
- t.Errorf("gray must be included in the results but not")
- }
- }
- func TestImageDrawTrianglesWithSubImage(t *testing.T) {
- const w, h = 16, 16
- src := ebiten.NewImage(w, h)
- dst := ebiten.NewImage(w, h)
- pix := make([]byte, 4*w*h)
- for j := 0; j < h; j++ {
- for i := 0; i < w; i++ {
- if 4 <= i && i < 8 && 4 <= j && j < 8 {
- pix[4*(i+j*w)] = 0xff
- pix[4*(i+j*w)+1] = 0
- pix[4*(i+j*w)+2] = 0
- pix[4*(i+j*w)+3] = 0xff
- } else {
- pix[4*(i+j*w)] = 0
- pix[4*(i+j*w)+1] = 0xff
- pix[4*(i+j*w)+2] = 0
- pix[4*(i+j*w)+3] = 0xff
- }
- }
- }
- src.WritePixels(pix)
- vs := []ebiten.Vertex{
- {
- DstX: 0,
- DstY: 0,
- SrcX: 0,
- SrcY: 0,
- ColorR: 1,
- ColorG: 1,
- ColorB: 1,
- ColorA: 1,
- },
- {
- DstX: w,
- DstY: 0,
- SrcX: w,
- SrcY: 0,
- ColorR: 1,
- ColorG: 1,
- ColorB: 1,
- ColorA: 1,
- },
- {
- DstX: 0,
- DstY: h,
- SrcX: 0,
- SrcY: h,
- ColorR: 1,
- ColorG: 1,
- ColorB: 1,
- ColorA: 1,
- },
- {
- DstX: w,
- DstY: h,
- SrcX: w,
- SrcY: h,
- ColorR: 1,
- ColorG: 1,
- ColorB: 1,
- ColorA: 1,
- },
- }
- is := []uint16{0, 1, 2, 1, 2, 3}
- op := &ebiten.DrawTrianglesOptions{}
- op.Address = ebiten.AddressClampToZero
- dst.DrawTriangles(vs, is, src.SubImage(image.Rect(4, 4, 8, 8)).(*ebiten.Image), op)
- for j := 0; j < h; j++ {
- for i := 0; i < w; i++ {
- got := dst.At(i, j).(color.RGBA)
- var want color.RGBA
- if 4 <= i && i < 8 && 4 <= j && j < 8 {
- want = src.At(i, j).(color.RGBA)
- }
- if !sameColors(got, want, 1) {
- t.Errorf("dst.At(%d, %d): got %v, want: %v", i, j, got, want)
- }
- }
- }
- }
- // Issue #823
- func TestImageAtAfterDisposingSubImage(t *testing.T) {
- img := ebiten.NewImage(16, 16)
- img.Set(0, 0, color.White)
- img.SubImage(image.Rect(0, 0, 16, 16))
- runtime.GC()
- want := color.RGBA{R: 0xff, G: 0xff, B: 0xff, A: 0xff}
- want64 := color.RGBA64{R: 0xffff, G: 0xffff, B: 0xffff, A: 0xffff}
- got := img.At(0, 0)
- if got != want {
- t.Errorf("At(0,0) got: %v, want: %v", got, want)
- }
- got = img.RGBA64At(0, 0)
- if got != want64 {
- t.Errorf("RGBA64At(0,0) got: %v, want: %v", got, want)
- }
- img.Set(0, 1, color.White)
- sub := img.SubImage(image.Rect(0, 0, 16, 16)).(*ebiten.Image)
- sub.Dispose()
- got = img.At(0, 1)
- if got != want {
- t.Errorf("At(0,1) got: %v, want: %v", got, want64)
- }
- got = img.RGBA64At(0, 1)
- if got != want64 {
- t.Errorf("RGBA64At(0,1) got: %v, want: %v", got, want64)
- }
- }
- func TestImageAtAfterDeallocateSubImage(t *testing.T) {
- img := ebiten.NewImage(16, 16)
- img.Set(0, 0, color.White)
- img.SubImage(image.Rect(0, 0, 16, 16))
- runtime.GC()
- want := color.RGBA{R: 0xff, G: 0xff, B: 0xff, A: 0xff}
- want64 := color.RGBA64{R: 0xffff, G: 0xffff, B: 0xffff, A: 0xffff}
- got := img.At(0, 0)
- if got != want {
- t.Errorf("At(0,0) got: %v, want: %v", got, want)
- }
- got = img.RGBA64At(0, 0)
- if got != want64 {
- t.Errorf("RGBA64At(0,0) got: %v, want: %v", got, want)
- }
- img.Set(0, 1, color.White)
- sub := img.SubImage(image.Rect(0, 0, 16, 16)).(*ebiten.Image)
- sub.Deallocate()
- got = img.At(0, 1)
- if got != want {
- t.Errorf("At(0,1) got: %v, want: %v", got, want64)
- }
- got = img.RGBA64At(0, 1)
- if got != want64 {
- t.Errorf("RGBA64At(0,1) got: %v, want: %v", got, want64)
- }
- }
- func TestImageSubImageSubImage(t *testing.T) {
- img := ebiten.NewImage(16, 16)
- img.Fill(color.White)
- sub0 := img.SubImage(image.Rect(0, 0, 12, 12)).(*ebiten.Image)
- sub1 := sub0.SubImage(image.Rect(4, 4, 16, 16)).(*ebiten.Image)
- cases := []struct {
- X int
- Y int
- Color color.RGBA
- }{
- {
- X: 0,
- Y: 0,
- Color: color.RGBA{},
- },
- {
- X: 4,
- Y: 4,
- Color: color.RGBA{R: 0xff, G: 0xff, B: 0xff, A: 0xff},
- },
- {
- X: 15,
- Y: 15,
- Color: color.RGBA{},
- },
- }
- for _, c := range cases {
- got := sub1.At(c.X, c.Y)
- want := c.Color
- if got != want {
- t.Errorf("At(%d, %d): got: %v, want: %v", c.X, c.Y, got, want)
- }
- }
- }
- // Issue #839
- func TestImageTooSmallMipmap(t *testing.T) {
- const w, h = 16, 16
- src := ebiten.NewImage(w, h)
- dst := ebiten.NewImage(w, h)
- src.Fill(color.White)
- op := &ebiten.DrawImageOptions{}
- op.GeoM.Scale(1, 0.24)
- op.Filter = ebiten.FilterLinear
- dst.DrawImage(src.SubImage(image.Rect(5, 0, 6, 16)).(*ebiten.Image), op)
- got := dst.At(0, 0).(color.RGBA)
- want := color.RGBA{R: 0xff, G: 0xff, B: 0xff, A: 0xff}
- if got != want {
- t.Errorf("got: %v, want: %v", got, want)
- }
- }
- func TestImageZeroSizedMipmap(t *testing.T) {
- const w, h = 16, 16
- src := ebiten.NewImage(w, h)
- dst := ebiten.NewImage(w, h)
- op := &ebiten.DrawImageOptions{}
- op.Filter = ebiten.FilterLinear
- dst.DrawImage(src.SubImage(image.ZR).(*ebiten.Image), op)
- }
- // Issue #898
- func TestImageFillingAndEdges(t *testing.T) {
- const (
- srcw, srch = 16, 16
- dstw, dsth = 256, 16
- )
- src := ebiten.NewImage(srcw, srch)
- dst := ebiten.NewImage(dstw, dsth)
- src.Fill(color.White)
- dst.Fill(color.Black)
- op := &ebiten.DrawImageOptions{}
- op.GeoM.Scale(float64(dstw-2)/float64(srcw), float64(dsth-2)/float64(srch))
- op.GeoM.Translate(1, 1)
- dst.DrawImage(src, op)
- for j := 0; j < dsth; j++ {
- for i := 0; i < dstw; i++ {
- got := dst.At(i, j).(color.RGBA)
- want := color.RGBA{R: 0xff, G: 0xff, B: 0xff, A: 0xff}
- if i == 0 || i == dstw-1 || j == 0 || j == dsth-1 {
- want = color.RGBA{A: 0xff}
- }
- if got != want {
- t.Errorf("dst.At(%d, %d): got: %v, want: %v", i, j, got, want)
- }
- }
- }
- }
- func TestImageDrawTrianglesAndMutateArgs(t *testing.T) {
- const w, h = 16, 16
- dst := ebiten.NewImage(w, h)
- src := ebiten.NewImage(w, h)
- clr := color.RGBA{R: 0xff, A: 0xff}
- src.Fill(clr)
- vs := []ebiten.Vertex{
- {
- DstX: 0,
- DstY: 0,
- SrcX: 0,
- SrcY: 0,
- ColorR: 1,
- ColorG: 1,
- ColorB: 1,
- ColorA: 1,
- },
- {
- DstX: w,
- DstY: 0,
- SrcX: w,
- SrcY: 0,
- ColorR: 1,
- ColorG: 1,
- ColorB: 1,
- ColorA: 1,
- },
- {
- DstX: 0,
- DstY: h,
- SrcX: 0,
- SrcY: h,
- ColorR: 1,
- ColorG: 1,
- ColorB: 1,
- ColorA: 1,
- },
- {
- DstX: w,
- DstY: h,
- SrcX: w,
- SrcY: h,
- ColorR: 1,
- ColorG: 1,
- ColorB: 1,
- ColorA: 1,
- },
- }
- is := []uint16{0, 1, 2, 1, 2, 3}
- dst.DrawTriangles(vs, is, src, nil)
- vs[0].SrcX = w
- vs[0].SrcY = h
- is[5] = 0
- for j := 0; j < w; j++ {
- for i := 0; i < w; i++ {
- got := dst.At(i, j)
- want := clr
- if got != want {
- t.Errorf("dst.At(%d, %d): got %v, want %v", i, j, got, want)
- }
- }
- }
- }
- func TestImageWritePixelsOnSubImage(t *testing.T) {
- dst := ebiten.NewImage(17, 31)
- dst.Fill(color.RGBA{R: 0xff, A: 0xff})
- pix0 := make([]byte, 4*5*3)
- idx := 0
- for j := 0; j < 3; j++ {
- for i := 0; i < 5; i++ {
- pix0[4*idx] = 0
- pix0[4*idx+1] = 0xff
- pix0[4*idx+2] = 0
- pix0[4*idx+3] = 0xff
- idx++
- }
- }
- r0 := image.Rect(4, 5, 9, 8)
- dst.SubImage(r0).(*ebiten.Image).WritePixels(pix0)
- pix1 := make([]byte, 4*5*3)
- idx = 0
- for j := 0; j < 3; j++ {
- for i := 0; i < 5; i++ {
- pix1[4*idx] = 0
- pix1[4*idx+1] = 0
- pix1[4*idx+2] = 0xff
- pix1[4*idx+3] = 0xff
- idx++
- }
- }
- r1 := image.Rect(11, 10, 16, 13)
- dst.SubImage(r1).(*ebiten.Image).WritePixels(pix1)
- // Clear the pixels. This should not affect the result.
- idx = 0
- for j := 0; j < 3; j++ {
- for i := 0; i < 5; i++ {
- pix1[4*idx] = 0
- pix1[4*idx+1] = 0
- pix1[4*idx+2] = 0
- pix1[4*idx+3] = 0
- idx++
- }
- }
- for j := 0; j < 31; j++ {
- for i := 0; i < 17; i++ {
- got := dst.At(i, j).(color.RGBA)
- want := color.RGBA{R: 0xff, A: 0xff}
- p := image.Pt(i, j)
- switch {
- case p.In(r0):
- want = color.RGBA{G: 0xff, A: 0xff}
- case p.In(r1):
- want = color.RGBA{B: 0xff, A: 0xff}
- }
- if got != want {
- t.Errorf("dst.At(%d, %d): got: %v, want: %v", i, j, got, want)
- }
- }
- }
- }
- func TestImageDrawTrianglesWithColorM(t *testing.T) {
- const w, h = 16, 16
- dst0 := ebiten.NewImage(w, h)
- src := ebiten.NewImage(w, h)
- src.Fill(color.White)
- vs0 := []ebiten.Vertex{
- {
- DstX: 0,
- DstY: 0,
- SrcX: 0,
- SrcY: 0,
- ColorR: 1,
- ColorG: 1,
- ColorB: 1,
- ColorA: 1,
- },
- {
- DstX: w,
- DstY: 0,
- SrcX: w,
- SrcY: 0,
- ColorR: 1,
- ColorG: 1,
- ColorB: 1,
- ColorA: 1,
- },
- {
- DstX: 0,
- DstY: h,
- SrcX: 0,
- SrcY: h,
- ColorR: 1,
- ColorG: 1,
- ColorB: 1,
- ColorA: 1,
- },
- {
- DstX: w,
- DstY: h,
- SrcX: w,
- SrcY: h,
- ColorR: 1,
- ColorG: 1,
- ColorB: 1,
- ColorA: 1,
- },
- }
- op := &ebiten.DrawTrianglesOptions{}
- op.ColorM.Scale(0.2, 0.4, 0.6, 0.8)
- is := []uint16{0, 1, 2, 1, 2, 3}
- dst0.DrawTriangles(vs0, is, src, op)
- for _, format := range []ebiten.ColorScaleMode{
- ebiten.ColorScaleModeStraightAlpha,
- ebiten.ColorScaleModePremultipliedAlpha,
- } {
- format := format
- t.Run(fmt.Sprintf("format%d", format), func(t *testing.T) {
- var cr, cg, cb, ca float32
- switch format {
- case ebiten.ColorScaleModeStraightAlpha:
- // The values are the same as ColorM.Scale
- cr = 0.2
- cg = 0.4
- cb = 0.6
- ca = 0.8
- case ebiten.ColorScaleModePremultipliedAlpha:
- cr = 0.2 * 0.8
- cg = 0.4 * 0.8
- cb = 0.6 * 0.8
- ca = 0.8
- }
- vs1 := []ebiten.Vertex{
- {
- DstX: 0,
- DstY: 0,
- SrcX: 0,
- SrcY: 0,
- ColorR: cr,
- ColorG: cg,
- ColorB: cb,
- ColorA: ca,
- },
- {
- DstX: w,
- DstY: 0,
- SrcX: w,
- SrcY: 0,
- ColorR: cr,
- ColorG: cg,
- ColorB: cb,
- ColorA: ca,
- },
- {
- DstX: 0,
- DstY: h,
- SrcX: 0,
- SrcY: h,
- ColorR: cr,
- ColorG: cg,
- ColorB: cb,
- ColorA: ca,
- },
- {
- DstX: w,
- DstY: h,
- SrcX: w,
- SrcY: h,
- ColorR: cr,
- ColorG: cg,
- ColorB: cb,
- ColorA: ca,
- },
- }
- dst1 := ebiten.NewImage(w, h)
- op := &ebiten.DrawTrianglesOptions{}
- op.ColorScaleMode = format
- dst1.DrawTriangles(vs1, is, src, op)
- for j := 0; j < h; j++ {
- for i := 0; i < w; i++ {
- got := dst0.At(i, j)
- want := dst1.At(i, j)
- if got != want {
- t.Errorf("At(%d, %d): got: %v, want: %v", i, j, got, want)
- }
- }
- }
- })
- }
- }
- func TestImageDrawTrianglesInterpolatesColors(t *testing.T) {
- const w, h = 3, 1
- src := ebiten.NewImage(w, h)
- src.Fill(color.White)
- vs := []ebiten.Vertex{
- {
- DstX: 0,
- DstY: 0,
- SrcX: 0,
- SrcY: 0,
- ColorR: 1,
- ColorG: 0,
- ColorB: 0,
- ColorA: 0,
- },
- {
- DstX: w,
- DstY: 0,
- SrcX: w,
- SrcY: 0,
- ColorR: 0,
- ColorG: 1,
- ColorB: 0,
- ColorA: 1,
- },
- {
- DstX: 0,
- DstY: h,
- SrcX: 0,
- SrcY: h,
- ColorR: 1,
- ColorG: 0,
- ColorB: 0,
- ColorA: 0,
- },
- {
- DstX: w,
- DstY: h,
- SrcX: w,
- SrcY: h,
- ColorR: 0,
- ColorG: 1,
- ColorB: 0,
- ColorA: 1,
- },
- }
- for _, format := range []ebiten.ColorScaleMode{
- ebiten.ColorScaleModeStraightAlpha,
- ebiten.ColorScaleModePremultipliedAlpha,
- } {
- format := format
- t.Run(fmt.Sprintf("format%d", format), func(t *testing.T) {
- dst := ebiten.NewImage(w, h)
- dst.Fill(color.RGBA{B: 0xff, A: 0xff})
- op := &ebiten.DrawTrianglesOptions{}
- op.ColorScaleMode = format
- is := []uint16{0, 1, 2, 1, 2, 3}
- dst.DrawTriangles(vs, is, src, op)
- got := dst.At(1, 0).(color.RGBA)
- // Correct color interpolation uses the alpha channel
- // and notices that colors on the left side of the texture are fully transparent.
- var want color.RGBA
- switch format {
- case ebiten.ColorScaleModeStraightAlpha:
- want = color.RGBA{G: 0x80, B: 0x80, A: 0xff}
- case ebiten.ColorScaleModePremultipliedAlpha:
- want = color.RGBA{R: 0x80, G: 0x80, B: 0x80, A: 0xff}
- }
- if !sameColors(got, want, 2) {
- t.Errorf("At(1, 0): got: %v, want: %v", got, want)
- }
- })
- }
- }
- func TestImageDrawTrianglesShaderInterpolatesValues(t *testing.T) {
- const w, h = 3, 1
- src := ebiten.NewImage(w, h)
- dst := ebiten.NewImage(w, h)
- src.Fill(color.White)
- vs := []ebiten.Vertex{
- {
- DstX: 0,
- DstY: 0,
- SrcX: 0,
- SrcY: 0,
- ColorR: 1,
- ColorG: 0,
- ColorB: 0,
- ColorA: 0,
- },
- {
- DstX: w,
- DstY: 0,
- SrcX: w,
- SrcY: 0,
- ColorR: 0,
- ColorG: 1,
- ColorB: 0,
- ColorA: 1,
- },
- {
- DstX: 0,
- DstY: h,
- SrcX: 0,
- SrcY: h,
- ColorR: 1,
- ColorG: 0,
- ColorB: 0,
- ColorA: 0,
- },
- {
- DstX: w,
- DstY: h,
- SrcX: w,
- SrcY: h,
- ColorR: 0,
- ColorG: 1,
- ColorB: 0,
- ColorA: 1,
- },
- }
- dst.Fill(color.RGBA{B: 0xff, A: 0xff})
- op := &ebiten.DrawTrianglesShaderOptions{
- Images: [4]*ebiten.Image{src, nil, nil, nil},
- }
- is := []uint16{0, 1, 2, 1, 2, 3}
- shader, err := ebiten.NewShader([]byte(`
- package main
- func Fragment(dstPos vec4, srcPos vec2, color vec4) vec4 {
- return color
- }
- `))
- if err != nil {
- t.Fatalf("could not compile shader: %v", err)
- }
- dst.DrawTrianglesShader(vs, is, shader, op)
- got := dst.At(1, 0).(color.RGBA)
- // Shaders get each color value interpolated independently.
- want := color.RGBA{R: 0x80, G: 0x80, B: 0x80, A: 0xff}
- if !sameColors(got, want, 2) {
- t.Errorf("At(1, 0): got: %v, want: %v", got, want)
- }
- }
- // Issue #1137
- func TestImageDrawOver(t *testing.T) {
- const (
- w = 320
- h = 240
- )
- dst := ebiten.NewImage(w, h)
- src := image.NewUniform(color.RGBA{R: 0xff, A: 0xff})
- // This must not cause infinite-loop.
- draw.Draw(dst, dst.Bounds(), src, image.ZP, draw.Over)
- for j := 0; j < h; j++ {
- for i := 0; i < w; i++ {
- got := dst.At(i, j)
- want := color.RGBA{R: 0xff, A: 0xff}
- if got != want {
- t.Errorf("At(%d, %d): got: %v, want: %v", i, j, got, want)
- }
- }
- }
- }
- func TestImageDrawDisposedImage(t *testing.T) {
- defer func() {
- if r := recover(); r == nil {
- t.Errorf("DrawImage must panic but not")
- }
- }()
- dst := ebiten.NewImage(16, 16)
- src := ebiten.NewImage(16, 16)
- src.Dispose()
- dst.DrawImage(src, nil)
- }
- func TestImageDrawDeallocatedImage(t *testing.T) {
- dst := ebiten.NewImage(16, 16)
- src := ebiten.NewImage(16, 16)
- src.Deallocate()
- // DrawImage must not panic.
- dst.DrawImage(src, nil)
- }
- func TestImageDrawTrianglesDisposedImage(t *testing.T) {
- defer func() {
- if r := recover(); r == nil {
- t.Errorf("DrawTriangles must panic but not")
- }
- }()
- dst := ebiten.NewImage(16, 16)
- src := ebiten.NewImage(16, 16)
- src.Dispose()
- vs := make([]ebiten.Vertex, 4)
- is := []uint16{0, 1, 2, 1, 2, 3}
- dst.DrawTriangles(vs, is, src, nil)
- }
- func TestImageDrawTrianglesDeallocateImage(t *testing.T) {
- dst := ebiten.NewImage(16, 16)
- src := ebiten.NewImage(16, 16)
- src.Deallocate()
- vs := make([]ebiten.Vertex, 4)
- is := []uint16{0, 1, 2, 1, 2, 3}
- // DrawTriangles must not panic.
- dst.DrawTriangles(vs, is, src, nil)
- }
- // #1137
- func BenchmarkImageDrawOver(b *testing.B) {
- dst := ebiten.NewImage(16, 16)
- src := image.NewUniform(color.Black)
- for n := 0; n < b.N; n++ {
- draw.Draw(dst, dst.Bounds(), src, image.ZP, draw.Over)
- }
- }
- // Issue #1171
- func TestImageFloatTranslate(t *testing.T) {
- const w, h = 32, 32
- for s := 2; s <= 8; s++ {
- s := s
- t.Run(fmt.Sprintf("scale%d", s), func(t *testing.T) {
- check := func(src *ebiten.Image) {
- dst := ebiten.NewImage(w*(s+1), h*(s+1))
- dst.Fill(color.RGBA{R: 0xff, A: 0xff})
- op := &ebiten.DrawImageOptions{}
- op.GeoM.Scale(float64(s), float64(s))
- op.GeoM.Translate(0, 0.501)
- dst.DrawImage(src, op)
- for j := 0; j < h*s+1; j++ {
- for i := 0; i < w*s; i++ {
- got := dst.At(i, j)
- x := byte(0xff)
- if j > 0 {
- x = (byte(j) - 1) / byte(s)
- }
- want := color.RGBA{R: x, A: 0xff}
- if got != want {
- t.Errorf("At(%d, %d): got: %v, want: %v", i, j, got, want)
- }
- }
- }
- }
- t.Run("image", func(t *testing.T) {
- src := ebiten.NewImage(w, h)
- pix := make([]byte, 4*w*h)
- for j := 0; j < h; j++ {
- for i := 0; i < w; i++ {
- pix[4*(j*w+i)] = byte(j)
- pix[4*(j*w+i)+3] = 0xff
- }
- }
- src.WritePixels(pix)
- check(src)
- })
- t.Run("subimage", func(t *testing.T) {
- src := ebiten.NewImage(w*s, h*s)
- pix := make([]byte, 4*(w*s)*(h*s))
- for j := 0; j < h*s; j++ {
- for i := 0; i < w*s; i++ {
- pix[4*(j*(w*s)+i)] = byte(j)
- pix[4*(j*(w*s)+i)+3] = 0xff
- }
- }
- src.WritePixels(pix)
- check(src.SubImage(image.Rect(0, 0, w, h)).(*ebiten.Image))
- })
- })
- }
- }
- // Issue #1213
- func TestImageColorMCopy(t *testing.T) {
- const w, h = 16, 16
- dst := ebiten.NewImage(w, h)
- src := ebiten.NewImage(w, h)
- for k := 0; k < 256; k++ {
- op := &ebiten.DrawImageOptions{}
- op.ColorM.Translate(1, 1, 1, float64(k)/0xff)
- op.Blend = ebiten.BlendCopy
- dst.DrawImage(src, op)
- for j := 0; j < h; j++ {
- for i := 0; i < w; i++ {
- got := dst.At(i, j).(color.RGBA)
- want := color.RGBA{R: byte(k), G: byte(k), B: byte(k), A: byte(k)}
- if !sameColors(got, want, 1) {
- t.Fatalf("dst.At(%d, %d), k: %d: got %v, want %v", i, j, k, got, want)
- }
- }
- }
- }
- }
- // TODO: Do we have to guarantee this behavior? See #1222
- func TestImageWritePixelsAndModifyPixels(t *testing.T) {
- const w, h = 16, 16
- dst := ebiten.NewImage(w, h)
- src := ebiten.NewImage(w, h)
- pix := make([]byte, 4*w*h)
- for j := 0; j < h; j++ {
- for i := 0; i < w; i++ {
- idx := 4 * (i + j*w)
- pix[idx] = 0xff
- pix[idx+1] = 0
- pix[idx+2] = 0
- pix[idx+3] = 0xff
- }
- }
- src.WritePixels(pix)
- // Modify pix after WritePixels
- for j := 0; j < h; j++ {
- for i := 0; i < w; i++ {
- idx := 4 * (i + j*w)
- pix[idx] = 0
- pix[idx+1] = 0xff
- pix[idx+2] = 0
- pix[idx+3] = 0xff
- }
- }
- // Ensure that src's pixels are actually used
- dst.DrawImage(src, nil)
- for j := 0; j < h; j++ {
- for i := 0; i < w; i++ {
- got := src.At(i, j).(color.RGBA)
- want := color.RGBA{R: 0xff, A: 0xff}
- if got != want {
- t.Errorf("src.At(%d, %d): got: %v, want: %v", i, j, got, want)
- }
- }
- }
- }
- func TestImageCompositeModeMultiply(t *testing.T) {
- const w, h = 16, 16
- dst := ebiten.NewImage(w, h)
- src := ebiten.NewImage(w, h)
- dst.Fill(color.RGBA{R: 0x10, G: 0x20, B: 0x30, A: 0x40})
- src.Fill(color.RGBA{R: 0x50, G: 0x60, B: 0x70, A: 0x80})
- op := &ebiten.DrawImageOptions{}
- op.CompositeMode = ebiten.CompositeModeMultiply
- dst.DrawImage(src, op)
- for j := 0; j < h; j++ {
- for i := 0; i < w; i++ {
- got := dst.At(i, j).(color.RGBA)
- want := color.RGBA{
- R: byte(math.Floor((0x10 / 255.0) * (0x50 / 255.0) * 255)),
- G: byte(math.Floor((0x20 / 255.0) * (0x60 / 255.0) * 255)),
- B: byte(math.Floor((0x30 / 255.0) * (0x70 / 255.0) * 255)),
- A: byte(math.Floor((0x40 / 255.0) * (0x80 / 255.0) * 255)),
- }
- if got != want {
- t.Errorf("dst.At(%d, %d): got: %v, want: %v", i, j, got, want)
- }
- }
- }
- }
- // Issue #1269
- func TestImageZeroTriangle(t *testing.T) {
- const w, h = 16, 16
- dst := ebiten.NewImage(w, h)
- src := ebiten.NewImage(1, 1)
- vs := []ebiten.Vertex{}
- is := []uint16{}
- dst.DrawTriangles(vs, is, src, nil)
- }
- // Issue #1398
- func TestImageDrawImageTooBigScale(t *testing.T) {
- dst := ebiten.NewImage(1, 1)
- src := ebiten.NewImage(1, 1)
- op := &ebiten.DrawImageOptions{}
- op.GeoM.Scale(1e20, 1e20)
- dst.DrawImage(src, op)
- }
- // Issue #1398
- func TestImageDrawImageTooSmallScale(t *testing.T) {
- dst := ebiten.NewImage(1, 1)
- src := ebiten.NewImage(1, 1)
- op := &ebiten.DrawImageOptions{}
- op.Filter = ebiten.FilterLinear
- op.GeoM.Scale(1e-10, 1e-10)
- dst.DrawImage(src, op)
- }
- // Issue #1399
- func TestImageDrawImageCannotAllocateImageForMipmap(t *testing.T) {
- dst := ebiten.NewImage(1, 1)
- src := ebiten.NewImage(maxImageSize, maxImageSize)
- op := &ebiten.DrawImageOptions{}
- op.GeoM.Scale(64, 64)
- dst.DrawImage(src, op)
- dst.At(0, 0)
- }
- func TestImageNewImageWithZeroSize(t *testing.T) {
- defer func() {
- if r := recover(); r == nil {
- t.Errorf("DrawImage must panic but not")
- }
- }()
- _ = ebiten.NewImage(0, 1)
- }
- func TestImageNewImageFromImageWithZeroSize(t *testing.T) {
- defer func() {
- if r := recover(); r == nil {
- t.Errorf("DrawImage must panic but not")
- }
- }()
- img := image.NewRGBA(image.Rect(0, 0, 0, 1))
- _ = ebiten.NewImageFromImage(img)
- }
- func TestImageClip(t *testing.T) {
- const (
- w = 16
- h = 16
- )
- dst := ebiten.NewImage(w, h)
- src := ebiten.NewImage(w, h)
- dst.Fill(color.RGBA{R: 0xff, A: 0xff})
- src.Fill(color.RGBA{G: 0xff, A: 0xff})
- dst.SubImage(image.Rect(4, 5, 12, 14)).(*ebiten.Image).DrawImage(src, nil)
- for j := 0; j < h; j++ {
- for i := 0; i < w; i++ {
- got := dst.At(i, j).(color.RGBA)
- want := color.RGBA{R: 0xff, A: 0xff}
- if 4 <= i && i < 12 && 5 <= j && j < 14 {
- want = color.RGBA{G: 0xff, A: 0xff}
- }
- if got != want {
- t.Errorf("dst.At(%d, %d): got: %v, want: %v", i, j, got, want)
- }
- }
- }
- }
- // Issue #1691
- func TestImageSubImageFill(t *testing.T) {
- dst := ebiten.NewImage(3, 3).SubImage(image.Rect(1, 1, 2, 2)).(*ebiten.Image)
- dst.Fill(color.White)
- for j := 0; j < 3; j++ {
- for i := 0; i < 3; i++ {
- got := dst.At(i, j)
- var want color.RGBA
- if i == 1 && j == 1 {
- want = color.RGBA{R: 0xff, G: 0xff, B: 0xff, A: 0xff}
- }
- if got != want {
- t.Errorf("dst.At(%d, %d): got: %v, want: %v", i, j, got, want)
- }
- }
- }
- dst = ebiten.NewImage(17, 31).SubImage(image.Rect(3, 4, 8, 10)).(*ebiten.Image)
- dst.Fill(color.White)
- for j := 0; j < 31; j++ {
- for i := 0; i < 17; i++ {
- got := dst.At(i, j)
- var want color.RGBA
- if 3 <= i && i < 8 && 4 <= j && j < 10 {
- want = color.RGBA{R: 0xff, G: 0xff, B: 0xff, A: 0xff}
- }
- if got != want {
- t.Errorf("dst.At(%d, %d): got: %v, want: %v", i, j, got, want)
- }
- }
- }
- }
- func TestImageEvenOdd(t *testing.T) {
- whiteImage := ebiten.NewImage(3, 3)
- whiteImage.Fill(color.White)
- emptySubImage := whiteImage.SubImage(image.Rect(1, 1, 2, 2)).(*ebiten.Image)
- vs0 := []ebiten.Vertex{
- {
- DstX: 1, DstY: 1, SrcX: 1, SrcY: 1,
- ColorR: 1, ColorG: 0, ColorB: 0, ColorA: 1,
- },
- {
- DstX: 15, DstY: 1, SrcX: 1, SrcY: 1,
- ColorR: 1, ColorG: 0, ColorB: 0, ColorA: 1,
- },
- {
- DstX: 1, DstY: 15, SrcX: 1, SrcY: 1,
- ColorR: 1, ColorG: 0, ColorB: 0, ColorA: 1,
- },
- {
- DstX: 15, DstY: 15, SrcX: 1, SrcY: 1,
- ColorR: 1, ColorG: 0, ColorB: 0, ColorA: 1,
- },
- }
- is0 := []uint16{0, 1, 2, 1, 2, 3}
- vs1 := []ebiten.Vertex{
- {
- DstX: 2, DstY: 2, SrcX: 1, SrcY: 1,
- ColorR: 0, ColorG: 1, ColorB: 0, ColorA: 1,
- },
- {
- DstX: 14, DstY: 2, SrcX: 1, SrcY: 1,
- ColorR: 0, ColorG: 1, ColorB: 0, ColorA: 1,
- },
- {
- DstX: 2, DstY: 14, SrcX: 1, SrcY: 1,
- ColorR: 0, ColorG: 1, ColorB: 0, ColorA: 1,
- },
- {
- DstX: 14, DstY: 14, SrcX: 1, SrcY: 1,
- ColorR: 0, ColorG: 1, ColorB: 0, ColorA: 1,
- },
- }
- is1 := []uint16{4, 5, 6, 5, 6, 7}
- vs2 := []ebiten.Vertex{
- {
- DstX: 3, DstY: 3, SrcX: 1, SrcY: 1,
- ColorR: 0, ColorG: 0, ColorB: 1, ColorA: 1,
- },
- {
- DstX: 13, DstY: 3, SrcX: 1, SrcY: 1,
- ColorR: 0, ColorG: 0, ColorB: 1, ColorA: 1,
- },
- {
- DstX: 3, DstY: 13, SrcX: 1, SrcY: 1,
- ColorR: 0, ColorG: 0, ColorB: 1, ColorA: 1,
- },
- {
- DstX: 13, DstY: 13, SrcX: 1, SrcY: 1,
- ColorR: 0, ColorG: 0, ColorB: 1, ColorA: 1,
- },
- }
- is2 := []uint16{8, 9, 10, 9, 10, 11}
- // Draw all the vertices once. The even-odd rule is applied for all the vertices once.
- dst := ebiten.NewImage(16, 16)
- op := &ebiten.DrawTrianglesOptions{
- FillRule: ebiten.FillRuleEvenOdd,
- }
- dst.DrawTriangles(append(append(vs0, vs1...), vs2...), append(append(is0, is1...), is2...), emptySubImage, op)
- for j := 0; j < 16; j++ {
- for i := 0; i < 16; i++ {
- got := dst.At(i, j)
- var want color.RGBA
- switch {
- case 3 <= i && i < 13 && 3 <= j && j < 13:
- want = color.RGBA{B: 0xff, A: 0xff}
- case 2 <= i && i < 14 && 2 <= j && j < 14:
- want = color.RGBA{}
- case 1 <= i && i < 15 && 1 <= j && j < 15:
- want = color.RGBA{R: 0xff, A: 0xff}
- default:
- want = color.RGBA{}
- }
- if got != want {
- t.Errorf("dst.At(%d, %d): got: %v, want: %v", i, j, got, want)
- }
- }
- }
- // Do the same thing but with a little shift. This confirms that the underlying stencil buffer is cleared correctly.
- for i := range vs0 {
- vs0[i].DstX++
- vs0[i].DstY++
- }
- for i := range vs1 {
- vs1[i].DstX++
- vs1[i].DstY++
- }
- for i := range vs2 {
- vs2[i].DstX++
- vs2[i].DstY++
- }
- dst.Clear()
- dst.DrawTriangles(append(append(vs0, vs1...), vs2...), append(append(is0, is1...), is2...), emptySubImage, op)
- for j := 0; j < 16; j++ {
- for i := 0; i < 16; i++ {
- got := dst.At(i, j)
- var want color.RGBA
- switch {
- case 4 <= i && i < 14 && 4 <= j && j < 14:
- want = color.RGBA{B: 0xff, A: 0xff}
- case 3 <= i && i < 15 && 3 <= j && j < 15:
- want = color.RGBA{}
- case 2 <= i && i < 16 && 2 <= j && j < 16:
- want = color.RGBA{R: 0xff, A: 0xff}
- default:
- want = color.RGBA{}
- }
- if got != want {
- t.Errorf("dst.At(%d, %d): got: %v, want: %v", i, j, got, want)
- }
- }
- }
- // Do the same thing but with split DrawTriangle calls. This confirms that the even-odd rule is applied for one call.
- for i := range vs0 {
- vs0[i].DstX--
- vs0[i].DstY--
- }
- for i := range vs1 {
- vs1[i].DstX--
- vs1[i].DstY--
- }
- for i := range vs2 {
- vs2[i].DstX--
- vs2[i].DstY--
- }
- dst.Clear()
- // Use the first indices set.
- dst.DrawTriangles(vs0, is0, emptySubImage, op)
- dst.DrawTriangles(vs1, is0, emptySubImage, op)
- dst.DrawTriangles(vs2, is0, emptySubImage, op)
- for j := 0; j < 16; j++ {
- for i := 0; i < 16; i++ {
- got := dst.At(i, j)
- var want color.RGBA
- switch {
- case 3 <= i && i < 13 && 3 <= j && j < 13:
- want = color.RGBA{B: 0xff, A: 0xff}
- case 2 <= i && i < 14 && 2 <= j && j < 14:
- want = color.RGBA{G: 0xff, A: 0xff}
- case 1 <= i && i < 15 && 1 <= j && j < 15:
- want = color.RGBA{R: 0xff, A: 0xff}
- default:
- want = color.RGBA{}
- }
- if got != want {
- t.Errorf("dst.At(%d, %d): got: %v, want: %v", i, j, got, want)
- }
- }
- }
- }
- func TestImageFillRule(t *testing.T) {
- for _, fillRule := range []ebiten.FillRule{ebiten.FillRuleFillAll, ebiten.FillRuleNonZero, ebiten.FillRuleEvenOdd} {
- fillRule := fillRule
- var name string
- switch fillRule {
- case ebiten.FillRuleFillAll:
- name = "FillAll"
- case ebiten.FillRuleNonZero:
- name = "NonZero"
- case ebiten.FillRuleEvenOdd:
- name = "EvenOdd"
- }
- t.Run(name, func(t *testing.T) {
- whiteImage := ebiten.NewImage(3, 3)
- whiteImage.Fill(color.White)
- emptySubImage := whiteImage.SubImage(image.Rect(1, 1, 2, 2)).(*ebiten.Image)
- // The outside rectangle (clockwise)
- vs0 := []ebiten.Vertex{
- {
- DstX: 1, DstY: 1, SrcX: 1, SrcY: 1,
- ColorR: 1, ColorG: 0, ColorB: 0, ColorA: 1,
- },
- {
- DstX: 15, DstY: 1, SrcX: 1, SrcY: 1,
- ColorR: 1, ColorG: 0, ColorB: 0, ColorA: 1,
- },
- {
- DstX: 15, DstY: 15, SrcX: 1, SrcY: 1,
- ColorR: 1, ColorG: 0, ColorB: 0, ColorA: 1,
- },
- {
- DstX: 1, DstY: 15, SrcX: 1, SrcY: 1,
- ColorR: 1, ColorG: 0, ColorB: 0, ColorA: 1,
- },
- }
- is0 := []uint16{0, 1, 2, 2, 3, 0}
- // An inside rectangle (clockwise)
- vs1 := []ebiten.Vertex{
- {
- DstX: 2, DstY: 2, SrcX: 1, SrcY: 1,
- ColorR: 0, ColorG: 1, ColorB: 0, ColorA: 1,
- },
- {
- DstX: 7, DstY: 2, SrcX: 1, SrcY: 1,
- ColorR: 0, ColorG: 1, ColorB: 0, ColorA: 1,
- },
- {
- DstX: 7, DstY: 7, SrcX: 1, SrcY: 1,
- ColorR: 0, ColorG: 1, ColorB: 0, ColorA: 1,
- },
- {
- DstX: 2, DstY: 7, SrcX: 1, SrcY: 1,
- ColorR: 0, ColorG: 1, ColorB: 0, ColorA: 1,
- },
- }
- is1 := []uint16{4, 5, 6, 6, 7, 4}
- // An inside rectangle (counter-clockwise)
- vs2 := []ebiten.Vertex{
- {
- DstX: 9, DstY: 9, SrcX: 1, SrcY: 1,
- ColorR: 0, ColorG: 0, ColorB: 1, ColorA: 1,
- },
- {
- DstX: 14, DstY: 9, SrcX: 1, SrcY: 1,
- ColorR: 0, ColorG: 0, ColorB: 1, ColorA: 1,
- },
- {
- DstX: 14, DstY: 14, SrcX: 1, SrcY: 1,
- ColorR: 0, ColorG: 0, ColorB: 1, ColorA: 1,
- },
- {
- DstX: 9, DstY: 14, SrcX: 1, SrcY: 1,
- ColorR: 0, ColorG: 0, ColorB: 1, ColorA: 1,
- },
- }
- is2 := []uint16{8, 11, 10, 10, 9, 8}
- // Draw all the vertices once. The even-odd rule is applied for all the vertices once.
- dst := ebiten.NewImage(16, 16)
- op := &ebiten.DrawTrianglesOptions{
- FillRule: fillRule,
- }
- dst.DrawTriangles(append(append(vs0, vs1...), vs2...), append(append(is0, is1...), is2...), emptySubImage, op)
- for j := 0; j < 16; j++ {
- for i := 0; i < 16; i++ {
- got := dst.At(i, j)
- var want color.RGBA
- switch {
- case 2 <= i && i < 7 && 2 <= j && j < 7:
- if fillRule != ebiten.FillRuleEvenOdd {
- want = color.RGBA{G: 0xff, A: 0xff}
- }
- case 9 <= i && i < 14 && 9 <= j && j < 14:
- if fillRule == ebiten.FillRuleFillAll {
- want = color.RGBA{B: 0xff, A: 0xff}
- }
- case 1 <= i && i < 15 && 1 <= j && j < 15:
- want = color.RGBA{R: 0xff, A: 0xff}
- }
- if got != want {
- t.Errorf("dst.At(%d, %d): got: %v, want: %v", i, j, got, want)
- }
- }
- }
- // Do the same thing but with a little shift. This confirms that the underlying stencil buffer is cleared correctly.
- for i := range vs0 {
- vs0[i].DstX++
- vs0[i].DstY++
- }
- for i := range vs1 {
- vs1[i].DstX++
- vs1[i].DstY++
- }
- for i := range vs2 {
- vs2[i].DstX++
- vs2[i].DstY++
- }
- dst.Clear()
- dst.DrawTriangles(append(append(vs0, vs1...), vs2...), append(append(is0, is1...), is2...), emptySubImage, op)
- for j := 0; j < 16; j++ {
- for i := 0; i < 16; i++ {
- got := dst.At(i, j)
- var want color.RGBA
- switch {
- case 3 <= i && i < 8 && 3 <= j && j < 8:
- if fillRule != ebiten.FillRuleEvenOdd {
- want = color.RGBA{G: 0xff, A: 0xff}
- }
- case 10 <= i && i < 15 && 10 <= j && j < 15:
- if fillRule == ebiten.FillRuleFillAll {
- want = color.RGBA{B: 0xff, A: 0xff}
- }
- case 2 <= i && i < 16 && 2 <= j && j < 16:
- want = color.RGBA{R: 0xff, A: 0xff}
- }
- if got != want {
- t.Errorf("dst.At(%d, %d): got: %v, want: %v", i, j, got, want)
- }
- }
- }
- // Do the same thing but with split DrawTriangle calls. This confirms that fill rules are applied for one call.
- for i := range vs0 {
- vs0[i].DstX--
- vs0[i].DstY--
- }
- for i := range vs1 {
- vs1[i].DstX--
- vs1[i].DstY--
- }
- for i := range vs2 {
- vs2[i].DstX--
- vs2[i].DstY--
- }
- dst.Clear()
- dst.DrawTriangles(vs0, []uint16{0, 1, 2, 2, 3, 0}, emptySubImage, op)
- dst.DrawTriangles(vs1, []uint16{0, 1, 2, 2, 3, 0}, emptySubImage, op)
- dst.DrawTriangles(vs2, []uint16{0, 3, 2, 2, 1, 0}, emptySubImage, op)
- for j := 0; j < 16; j++ {
- for i := 0; i < 16; i++ {
- got := dst.At(i, j)
- var want color.RGBA
- switch {
- case 2 <= i && i < 7 && 2 <= j && j < 7:
- want = color.RGBA{G: 0xff, A: 0xff}
- case 9 <= i && i < 14 && 9 <= j && j < 14:
- want = color.RGBA{B: 0xff, A: 0xff}
- case 1 <= i && i < 15 && 1 <= j && j < 15:
- want = color.RGBA{R: 0xff, A: 0xff}
- }
- if got != want {
- t.Errorf("dst.At(%d, %d): got: %v, want: %v", i, j, got, want)
- }
- }
- }
- })
- }
- }
- // #1658
- func BenchmarkColorMScale(b *testing.B) {
- r := rand.Float64
- dst := ebiten.NewImage(16, 16)
- src := ebiten.NewImage(16, 16)
- for n := 0; n < b.N; n++ {
- op := &ebiten.DrawImageOptions{}
- op.ColorM.Scale(r(), r(), r(), r())
- dst.DrawImage(src, op)
- }
- }
- func TestImageMoreIndicesThanMaxUint16(t *testing.T) {
- const (
- w = 16
- h = 16
- )
- dst := ebiten.NewImage(w, h)
- src := ebiten.NewImage(w, h)
- src.Fill(color.White)
- op := &ebiten.DrawTrianglesOptions{}
- vs := make([]ebiten.Vertex, 3)
- is := make([]uint16, 65538)
- dst.DrawTriangles(vs, is, src, op)
- // The next draw call should work well (and this is likely batched).
- vs = []ebiten.Vertex{
- {
- DstX: 0,
- DstY: 0,
- SrcX: 0,
- SrcY: 0,
- ColorR: 1,
- ColorG: 1,
- ColorB: 1,
- ColorA: 1,
- },
- {
- DstX: w,
- DstY: 0,
- SrcX: w,
- SrcY: 0,
- ColorR: 1,
- ColorG: 1,
- ColorB: 1,
- ColorA: 1,
- },
- {
- DstX: 0,
- DstY: h,
- SrcX: 0,
- SrcY: h,
- ColorR: 1,
- ColorG: 1,
- ColorB: 1,
- ColorA: 1,
- },
- {
- DstX: w,
- DstY: h,
- SrcX: w,
- SrcY: h,
- ColorR: 1,
- ColorG: 1,
- ColorB: 1,
- ColorA: 1,
- },
- }
- is = []uint16{0, 1, 2, 1, 2, 3}
- dst.DrawTriangles(vs, is, src, op)
- for j := 0; j < h; j++ {
- for i := 0; i < w; i++ {
- got := dst.At(i, j)
- want := color.RGBA{R: 0xff, G: 0xff, B: 0xff, A: 0xff}
- if got != want {
- t.Errorf("dst.At(%d, %d): got: %v, want: %v", i, j, got, want)
- }
- }
- }
- }
- func TestImageMoreVerticesThanMaxUint16(t *testing.T) {
- const (
- w = 16
- h = 16
- )
- dst := ebiten.NewImage(w, h)
- src := ebiten.NewImage(w, h)
- src.Fill(color.White)
- op := &ebiten.DrawTrianglesOptions{}
- vs := make([]ebiten.Vertex, math.MaxUint16+1)
- is := make([]uint16, 3)
- dst.DrawTriangles(vs, is, src, op)
- // The next draw call should work well (and this is likely batched).
- vs = []ebiten.Vertex{
- {
- DstX: 0,
- DstY: 0,
- SrcX: 0,
- SrcY: 0,
- ColorR: 1,
- ColorG: 1,
- ColorB: 1,
- ColorA: 1,
- },
- {
- DstX: w,
- DstY: 0,
- SrcX: w,
- SrcY: 0,
- ColorR: 1,
- ColorG: 1,
- ColorB: 1,
- ColorA: 1,
- },
- {
- DstX: 0,
- DstY: h,
- SrcX: 0,
- SrcY: h,
- ColorR: 1,
- ColorG: 1,
- ColorB: 1,
- ColorA: 1,
- },
- {
- DstX: w,
- DstY: h,
- SrcX: w,
- SrcY: h,
- ColorR: 1,
- ColorG: 1,
- ColorB: 1,
- ColorA: 1,
- },
- }
- is = []uint16{0, 1, 2, 1, 2, 3}
- dst.DrawTriangles(vs, is, src, op)
- for j := 0; j < h; j++ {
- for i := 0; i < w; i++ {
- got := dst.At(i, j)
- want := color.RGBA{R: 0xff, G: 0xff, B: 0xff, A: 0xff}
- if got != want {
- t.Errorf("dst.At(%d, %d): got: %v, want: %v", i, j, got, want)
- }
- }
- }
- }
- func TestImageNewImageFromEbitenImage(t *testing.T) {
- const (
- w = 16
- h = 16
- )
- pix := make([]byte, 4*w*h)
- for j := 0; j < h; j++ {
- for i := 0; i < w; i++ {
- idx := 4 * (i + j*w)
- pix[idx] = byte(i)
- pix[idx+1] = byte(j)
- pix[idx+2] = 0
- pix[idx+3] = 0xff
- }
- }
- img0 := ebiten.NewImage(w, h)
- img0.WritePixels(pix)
- img1 := ebiten.NewImageFromImage(img0)
- for j := 0; j < h; j++ {
- for i := 0; i < w; i++ {
- got := img1.At(i, j)
- want := color.RGBA{R: byte(i), G: byte(j), A: 0xff}
- if got != want {
- t.Errorf("img1.At(%d, %d): got: %v, want: %v", i, j, got, want)
- }
- }
- }
- img2 := ebiten.NewImageFromImage(img0.SubImage(image.Rect(4, 4, 12, 12)))
- for j := 0; j < h/2; j++ {
- for i := 0; i < w/2; i++ {
- got := img2.At(i, j)
- want := color.RGBA{R: byte(i + 4), G: byte(j + 4), A: 0xff}
- if got != want {
- t.Errorf("img1.At(%d, %d): got: %v, want: %v", i, j, got, want)
- }
- }
- }
- }
- func TestImageOptionsUnmanaged(t *testing.T) {
- const (
- w = 16
- h = 16
- )
- pix := make([]byte, 4*w*h)
- for j := 0; j < h; j++ {
- for i := 0; i < w; i++ {
- idx := 4 * (i + j*w)
- pix[idx] = byte(i)
- pix[idx+1] = byte(j)
- pix[idx+2] = 0
- pix[idx+3] = 0xff
- }
- }
- op := &ebiten.NewImageOptions{
- Unmanaged: true,
- }
- img := ebiten.NewImageWithOptions(image.Rect(0, 0, w, h), op)
- img.WritePixels(pix)
- for j := 0; j < h; j++ {
- for i := 0; i < w; i++ {
- got := img.At(i, j)
- want := color.RGBA{R: byte(i), G: byte(j), A: 0xff}
- if got != want {
- t.Errorf("img.At(%d, %d): got: %v, want: %v", i, j, got, want)
- }
- }
- }
- }
- func TestImageOptionsNegativeBoundsWritePixels(t *testing.T) {
- const (
- w = 16
- h = 16
- )
- pix0 := make([]byte, 4*w*h)
- for j := 0; j < h; j++ {
- for i := 0; i < w; i++ {
- idx := 4 * (i + j*w)
- pix0[idx] = byte(i)
- pix0[idx+1] = byte(j)
- pix0[idx+2] = 0
- pix0[idx+3] = 0xff
- }
- }
- const offset = -8
- img := ebiten.NewImageWithOptions(image.Rect(offset, offset, w+offset, h+offset), nil)
- img.WritePixels(pix0)
- for j := offset; j < h+offset; j++ {
- for i := offset; i < w+offset; i++ {
- got := img.At(i, j)
- want := color.RGBA{R: byte(i - offset), G: byte(j - offset), A: 0xff}
- if got != want {
- t.Errorf("img.At(%d, %d): got: %v, want: %v", i, j, got, want)
- }
- }
- }
- pix1 := make([]byte, 4*(w/2)*(h/2))
- for j := 0; j < h/2; j++ {
- for i := 0; i < w/2; i++ {
- idx := 4 * (i + j*w/2)
- pix1[idx] = 0
- pix1[idx+1] = 0
- pix1[idx+2] = 0xff
- pix1[idx+3] = 0xff
- }
- }
- const offset2 = -4
- sub := image.Rect(offset2, offset2, w/2+offset2, h/2+offset2)
- img.SubImage(sub).(*ebiten.Image).WritePixels(pix1)
- for j := offset; j < h+offset; j++ {
- for i := offset; i < w+offset; i++ {
- got := img.At(i, j)
- want := color.RGBA{R: byte(i - offset), G: byte(j - offset), A: 0xff}
- if image.Pt(i, j).In(sub) {
- want = color.RGBA{B: 0xff, A: 0xff}
- }
- if got != want {
- t.Errorf("img.At(%d, %d): got: %v, want: %v", i, j, got, want)
- }
- }
- }
- }
- func TestImageOptionsNegativeBoundsSet(t *testing.T) {
- const (
- w = 16
- h = 16
- )
- pix0 := make([]byte, 4*w*h)
- for j := 0; j < h; j++ {
- for i := 0; i < w; i++ {
- idx := 4 * (i + j*w)
- pix0[idx] = byte(i)
- pix0[idx+1] = byte(j)
- pix0[idx+2] = 0
- pix0[idx+3] = 0xff
- }
- }
- const offset = -8
- img := ebiten.NewImageWithOptions(image.Rect(offset, offset, w+offset, h+offset), nil)
- img.WritePixels(pix0)
- img.Set(-1, -2, color.RGBA{})
- for j := offset; j < h+offset; j++ {
- for i := offset; i < w+offset; i++ {
- got := img.At(i, j)
- want := color.RGBA{R: byte(i - offset), G: byte(j - offset), A: 0xff}
- if i == -1 && j == -2 {
- want = color.RGBA{}
- }
- if got != want {
- t.Errorf("img.At(%d, %d): got: %v, want: %v", i, j, got, want)
- }
- }
- }
- }
- func TestImageOptionsNegativeBoundsDrawImage(t *testing.T) {
- const (
- w = 16
- h = 16
- offset = -8
- )
- dst := ebiten.NewImageWithOptions(image.Rect(offset, offset, w+offset, h+offset), nil)
- src := ebiten.NewImageWithOptions(image.Rect(-1, -1, 1, 1), nil)
- pix := make([]byte, 4*2*2)
- for i := range pix {
- pix[i] = 0xff
- }
- src.WritePixels(pix)
- op := &ebiten.DrawImageOptions{}
- op.GeoM.Translate(-1, -1)
- op.GeoM.Scale(2, 3)
- dst.DrawImage(src, op)
- for j := offset; j < h+offset; j++ {
- for i := offset; i < w+offset; i++ {
- got := dst.At(i, j)
- var want color.RGBA
- if -2 <= i && i < 2 && -3 <= j && j < 3 {
- want = color.RGBA{R: 0xff, G: 0xff, B: 0xff, A: 0xff}
- }
- if got != want {
- t.Errorf("dst.At(%d, %d): got: %v, want: %v", i, j, got, want)
- }
- }
- }
- }
- func TestImageOptionsNegativeBoundsDrawTriangles(t *testing.T) {
- const (
- w = 16
- h = 16
- offset = -8
- )
- dst := ebiten.NewImageWithOptions(image.Rect(offset, offset, w+offset, h+offset), nil)
- src := ebiten.NewImageWithOptions(image.Rect(-1, -1, 1, 1), nil)
- pix := make([]byte, 4*2*2)
- for i := range pix {
- pix[i] = 0xff
- }
- src.WritePixels(pix)
- vs := []ebiten.Vertex{
- {
- DstX: -2,
- DstY: -3,
- SrcX: -1,
- SrcY: -1,
- ColorR: 1,
- ColorG: 1,
- ColorB: 1,
- ColorA: 1,
- },
- {
- DstX: 2,
- DstY: -3,
- SrcX: 1,
- SrcY: -1,
- ColorR: 1,
- ColorG: 1,
- ColorB: 1,
- ColorA: 1,
- },
- {
- DstX: -2,
- DstY: 3,
- SrcX: -1,
- SrcY: 1,
- ColorR: 1,
- ColorG: 1,
- ColorB: 1,
- ColorA: 1,
- },
- {
- DstX: 2,
- DstY: 3,
- SrcX: 1,
- SrcY: 1,
- ColorR: 1,
- ColorG: 1,
- ColorB: 1,
- ColorA: 1,
- },
- }
- is := []uint16{0, 1, 2, 1, 2, 3}
- dst.DrawTriangles(vs, is, src, nil)
- for j := offset; j < h+offset; j++ {
- for i := offset; i < w+offset; i++ {
- got := dst.At(i, j)
- var want color.RGBA
- if -2 <= i && i < 2 && -3 <= j && j < 3 {
- want = color.RGBA{R: 0xff, G: 0xff, B: 0xff, A: 0xff}
- }
- if got != want {
- t.Errorf("dst.At(%d, %d): got: %v, want: %v", i, j, got, want)
- }
- }
- }
- }
- func TestImageFromImageOptions(t *testing.T) {
- r := image.Rect(-2, -3, 4, 5)
- pix := make([]byte, 4*r.Dx()*r.Dy())
- for i := range pix {
- pix[i] = 0xff
- }
- src := &image.RGBA{
- Pix: pix,
- Stride: 4 * 2,
- Rect: r,
- }
- op := &ebiten.NewImageFromImageOptions{
- PreserveBounds: true,
- }
- img := ebiten.NewImageFromImageWithOptions(src, op)
- if got, want := img.Bounds(), r; got != want {
- t.Errorf("got: %v, want: %v", got, want)
- }
- for j := r.Min.Y; j < r.Max.Y; j++ {
- for i := r.Min.X; i < r.Max.X; i++ {
- got := img.At(i, j)
- want := color.RGBA{R: 0xff, G: 0xff, B: 0xff, A: 0xff}
- if got != want {
- t.Errorf("img.At(%d, %d): got: %v, want: %v", i, j, got, want)
- }
- }
- }
- }
- func TestImageFromEbitenImageOptions(t *testing.T) {
- r := image.Rect(-2, -3, 4, 5)
- src := ebiten.NewImageWithOptions(r, nil)
- pix := make([]byte, 4*r.Dx()*r.Dy())
- for i := range pix {
- pix[i] = 0xff
- }
- src.WritePixels(pix)
- op := &ebiten.NewImageFromImageOptions{
- PreserveBounds: true,
- }
- img := ebiten.NewImageFromImageWithOptions(src, op)
- if got, want := img.Bounds(), r; got != want {
- t.Errorf("got: %v, want: %v", got, want)
- }
- for j := r.Min.Y; j < r.Max.Y; j++ {
- for i := r.Min.X; i < r.Max.X; i++ {
- got := img.At(i, j)
- want := color.RGBA{R: 0xff, G: 0xff, B: 0xff, A: 0xff}
- if got != want {
- t.Errorf("img.At(%d, %d): got: %v, want: %v", i, j, got, want)
- }
- }
- }
- }
- // Issue #2159
- func TestImageOptionsFill(t *testing.T) {
- r0 := image.Rect(-2, -3, 4, 5)
- img := ebiten.NewImageWithOptions(r0, nil)
- img.Fill(color.RGBA{R: 0xff, A: 0xff})
- for j := r0.Min.Y; j < r0.Max.Y; j++ {
- for i := r0.Min.X; i < r0.Max.X; i++ {
- got := img.At(i, j)
- want := color.RGBA{R: 0xff, A: 0xff}
- if got != want {
- t.Errorf("img.At(%d, %d): got: %v, want: %v", i, j, got, want)
- }
- }
- }
- r1 := image.Rect(-1, -2, 3, 4)
- img.SubImage(r1).(*ebiten.Image).Fill(color.RGBA{G: 0xff, A: 0xff})
- for j := r0.Min.Y; j < r0.Max.Y; j++ {
- for i := r0.Min.X; i < r0.Max.X; i++ {
- got := img.At(i, j)
- want := color.RGBA{R: 0xff, A: 0xff}
- if image.Pt(i, j).In(r1) {
- want = color.RGBA{G: 0xff, A: 0xff}
- }
- if got != want {
- t.Errorf("img.At(%d, %d): got: %v, want: %v", i, j, got, want)
- }
- }
- }
- }
- // Issue #2159
- func TestImageOptionsClear(t *testing.T) {
- r0 := image.Rect(-2, -3, 4, 5)
- img := ebiten.NewImageWithOptions(r0, nil)
- img.Fill(color.RGBA{R: 0xff, A: 0xff})
- img.Clear()
- for j := r0.Min.Y; j < r0.Max.Y; j++ {
- for i := r0.Min.X; i < r0.Max.X; i++ {
- got := img.At(i, j)
- want := color.RGBA{}
- if got != want {
- t.Errorf("img.At(%d, %d): got: %v, want: %v", i, j, got, want)
- }
- }
- }
- img.Fill(color.RGBA{R: 0xff, A: 0xff})
- r1 := image.Rect(-1, -2, 3, 4)
- img.SubImage(r1).(*ebiten.Image).Clear()
- for j := r0.Min.Y; j < r0.Max.Y; j++ {
- for i := r0.Min.X; i < r0.Max.X; i++ {
- got := img.At(i, j)
- want := color.RGBA{R: 0xff, A: 0xff}
- if image.Pt(i, j).In(r1) {
- want = color.RGBA{}
- }
- if got != want {
- t.Errorf("img.At(%d, %d): got: %v, want: %v", i, j, got, want)
- }
- }
- }
- }
- // Issue #2178
- func TestImageTooManyDrawImage(t *testing.T) {
- src := ebiten.NewImage(1, 1)
- src.Fill(color.RGBA{R: 0xff, G: 0xff, B: 0xff, A: 0xff})
- const (
- w = 256
- h = 256
- )
- dst := ebiten.NewImage(w, h)
- op := &ebiten.DrawImageOptions{}
- for j := 0; j < h; j++ {
- for i := 0; i < w; i++ {
- op.GeoM.Reset()
- op.GeoM.Translate(float64(i), float64(j))
- dst.DrawImage(src, op)
- }
- }
- for j := 0; j < h; j++ {
- for i := 0; i < w; i++ {
- if got, want := dst.At(i, j), (color.RGBA{R: 0xff, G: 0xff, B: 0xff, A: 0xff}); got != want {
- t.Errorf("dst.At(%d, %d): got: %v, want: %v", i, j, got, want)
- }
- }
- }
- }
- func TestImageTooManyDrawImage2(t *testing.T) {
- src := ebiten.NewImage(1, 1)
- src.Fill(color.RGBA{R: 0xff, G: 0xff, B: 0xff, A: 0xff})
- const (
- w = 512
- h = 512
- )
- dst := ebiten.NewImage(w, h)
- posToColor := func(i, j int) color.RGBA {
- return color.RGBA{
- R: byte(i),
- G: byte(j),
- B: 0xff,
- A: 0xff,
- }
- }
- op := &ebiten.DrawImageOptions{}
- for j := 0; j < h; j++ {
- for i := 0; i < w; i++ {
- op.GeoM.Reset()
- op.GeoM.Translate(float64(i), float64(j))
- op.ColorScale.Reset()
- op.ColorScale.ScaleWithColor(posToColor(i, j))
- dst.DrawImage(src, op)
- }
- }
- for j := 0; j < h; j++ {
- for i := 0; i < w; i++ {
- if got, want := dst.At(i, j).(color.RGBA), posToColor(i, j); !sameColors(got, want, 1) {
- t.Errorf("dst.At(%d, %d): got: %v, want: %v", i, j, got, want)
- }
- }
- }
- }
- // Issue #2178
- func TestImageTooManyDrawTriangles(t *testing.T) {
- img := ebiten.NewImage(3, 3)
- img.Fill(color.RGBA{R: 0xff, G: 0xff, B: 0xff, A: 0xff})
- src := img.SubImage(image.Rect(1, 1, 2, 2)).(*ebiten.Image)
- const (
- w = 128
- h = 64
- )
- dst := ebiten.NewImage(w, h)
- var vertices []ebiten.Vertex
- var indices []uint16
- for j := 0; j < h; j++ {
- for i := 0; i < w; i++ {
- n := uint16(len(vertices))
- vertices = append(vertices,
- ebiten.Vertex{
- DstX: float32(i),
- DstY: float32(j),
- SrcX: 1,
- SrcY: 1,
- ColorR: 1,
- ColorG: 1,
- ColorB: 1,
- ColorA: 1,
- },
- ebiten.Vertex{
- DstX: float32(i) + 1,
- DstY: float32(j),
- SrcX: 2,
- SrcY: 1,
- ColorR: 1,
- ColorG: 1,
- ColorB: 1,
- ColorA: 1,
- },
- ebiten.Vertex{
- DstX: float32(i),
- DstY: float32(j) + 1,
- SrcX: 1,
- SrcY: 2,
- ColorR: 1,
- ColorG: 1,
- ColorB: 1,
- ColorA: 1,
- },
- ebiten.Vertex{
- DstX: float32(i) + 1,
- DstY: float32(j) + 1,
- SrcX: 2,
- SrcY: 2,
- ColorR: 1,
- ColorG: 1,
- ColorB: 1,
- ColorA: 1,
- },
- )
- indices = append(indices, n, n+1, n+2, n+1, n+2, n+3)
- }
- }
- dst.DrawTriangles(vertices, indices, src, nil)
- for j := 0; j < h; j++ {
- for i := 0; i < w; i++ {
- if got, want := dst.At(i, j), (color.RGBA{R: 0xff, G: 0xff, B: 0xff, A: 0xff}); got != want {
- t.Errorf("dst.At(%d, %d): got: %v, want: %v", i, j, got, want)
- }
- }
- }
- }
- func TestImageSetOverSet(t *testing.T) {
- img := ebiten.NewImage(1, 1)
- img.Set(0, 0, color.RGBA{R: 0xff, G: 0xff, B: 0xff, A: 0xff})
- if got, want := img.At(0, 0), (color.RGBA{R: 0xff, G: 0xff, B: 0xff, A: 0xff}); got != want {
- t.Errorf("got: %v, want: %v", got, want)
- }
- // Apply the change by 'Set' by calling DrawImage.
- dummy := ebiten.NewImage(1, 1)
- img.DrawImage(dummy, nil)
- if got, want := img.At(0, 0), (color.RGBA{R: 0xff, G: 0xff, B: 0xff, A: 0xff}); got != want {
- t.Errorf("got: %v, want: %v", got, want)
- }
- img.Set(0, 0, color.RGBA{R: 0x80, G: 0x80, B: 0x80, A: 0x80})
- if got, want := img.At(0, 0), (color.RGBA{R: 0x80, G: 0x80, B: 0x80, A: 0x80}); got != want {
- t.Errorf("got: %v, want: %v", got, want)
- }
- // Apply the change by 'Set' again.
- img.DrawImage(dummy, nil)
- if got, want := img.At(0, 0), (color.RGBA{R: 0x80, G: 0x80, B: 0x80, A: 0x80}); got != want {
- t.Errorf("got: %v, want: %v", got, want)
- }
- }
- // Issue #2204
- func TestImageTooManyConstantBuffersInDirectX(t *testing.T) {
- src := ebiten.NewImage(3, 3)
- src.Fill(color.White)
- src = src.SubImage(image.Rect(1, 1, 2, 2)).(*ebiten.Image)
- vs := []ebiten.Vertex{
- {
- DstX: 0, DstY: 0, SrcX: 1, SrcY: 1,
- ColorR: 1, ColorG: 1, ColorB: 1, ColorA: 1,
- },
- {
- DstX: 16, DstY: 0, SrcX: 1, SrcY: 1,
- ColorR: 1, ColorG: 1, ColorB: 1, ColorA: 1,
- },
- {
- DstX: 0, DstY: 16, SrcX: 1, SrcY: 1,
- ColorR: 1, ColorG: 1, ColorB: 1, ColorA: 1,
- },
- {
- DstX: 16, DstY: 16, SrcX: 1, SrcY: 1,
- ColorR: 1, ColorG: 1, ColorB: 1, ColorA: 1,
- },
- }
- is := []uint16{0, 1, 2, 1, 2, 3}
- dst0 := ebiten.NewImage(16, 16)
- dst1 := ebiten.NewImage(16, 16)
- op := &ebiten.DrawTrianglesOptions{
- FillRule: ebiten.FillRuleEvenOdd,
- }
- for i := 0; i < 100; i++ {
- dst0.DrawTriangles(vs, is, src, op)
- dst1.DrawTriangles(vs, is, src, op)
- }
- if got, want := dst0.At(0, 0), (color.RGBA{R: 0xff, G: 0xff, B: 0xff, A: 0xff}); got != want {
- t.Errorf("got: %v, want: %v", got, want)
- }
- if got, want := dst1.At(0, 0), (color.RGBA{R: 0xff, G: 0xff, B: 0xff, A: 0xff}); got != want {
- t.Errorf("got: %v, want: %v", got, want)
- }
- }
- func TestImageColorMAndScale(t *testing.T) {
- const w, h = 16, 16
- src := ebiten.NewImage(w, h)
- src.Fill(color.RGBA{R: 0x80, G: 0x80, B: 0x80, A: 0x80})
- vs := []ebiten.Vertex{
- {
- SrcX: 0,
- SrcY: 0,
- DstX: 0,
- DstY: 0,
- ColorR: 0.5,
- ColorG: 0.25,
- ColorB: 0.5,
- ColorA: 0.75,
- },
- {
- SrcX: w,
- SrcY: 0,
- DstX: w,
- DstY: 0,
- ColorR: 0.5,
- ColorG: 0.25,
- ColorB: 0.5,
- ColorA: 0.75,
- },
- {
- SrcX: 0,
- SrcY: h,
- DstX: 0,
- DstY: h,
- ColorR: 0.5,
- ColorG: 0.25,
- ColorB: 0.5,
- ColorA: 0.75,
- },
- {
- SrcX: w,
- SrcY: h,
- DstX: w,
- DstY: h,
- ColorR: 0.5,
- ColorG: 0.25,
- ColorB: 0.5,
- ColorA: 0.75,
- },
- }
- is := []uint16{0, 1, 2, 1, 2, 3}
- for _, format := range []ebiten.ColorScaleMode{
- ebiten.ColorScaleModeStraightAlpha,
- ebiten.ColorScaleModePremultipliedAlpha,
- } {
- format := format
- t.Run(fmt.Sprintf("format%d", format), func(t *testing.T) {
- dst := ebiten.NewImage(w, h)
- op := &ebiten.DrawTrianglesOptions{}
- op.ColorM.Translate(0.25, 0.25, 0.25, 0)
- op.ColorScaleMode = format
- dst.DrawTriangles(vs, is, src, op)
- got := dst.At(0, 0).(color.RGBA)
- alphaBeforeScale := 0.5
- var want color.RGBA
- switch format {
- case ebiten.ColorScaleModeStraightAlpha:
- want = color.RGBA{
- R: byte(math.Floor(0xff * (0.5/alphaBeforeScale + 0.25) * alphaBeforeScale * 0.5 * 0.75)),
- G: byte(math.Floor(0xff * (0.5/alphaBeforeScale + 0.25) * alphaBeforeScale * 0.25 * 0.75)),
- B: byte(math.Floor(0xff * (0.5/alphaBeforeScale + 0.25) * alphaBeforeScale * 0.5 * 0.75)),
- A: byte(math.Floor(0xff * alphaBeforeScale * 0.75)),
- }
- case ebiten.ColorScaleModePremultipliedAlpha:
- want = color.RGBA{
- R: byte(math.Floor(0xff * (0.5/alphaBeforeScale + 0.25) * alphaBeforeScale * 0.5)),
- G: byte(math.Floor(0xff * (0.5/alphaBeforeScale + 0.25) * alphaBeforeScale * 0.25)),
- B: byte(math.Floor(0xff * (0.5/alphaBeforeScale + 0.25) * alphaBeforeScale * 0.5)),
- A: byte(math.Floor(0xff * alphaBeforeScale * 0.75)),
- }
- }
- if !sameColors(got, want, 2) {
- t.Errorf("got: %v, want: %v", got, want)
- }
- })
- }
- }
- func TestImageBlendOperation(t *testing.T) {
- const w, h = 16, 1
- dst := ebiten.NewImage(w, h)
- src := ebiten.NewImage(w, h)
- dstColor := func(i int) (byte, byte, byte, byte) {
- return byte(4 * i * 17), byte(4*i*17 + 1), byte(4*i*17 + 2), byte(4*i*17 + 3)
- }
- srcColor := func(i int) (byte, byte, byte, byte) {
- return byte(4 * i * 13), byte(4*i*13 + 1), byte(4*i*13 + 2), byte(4*i*13 + 3)
- }
- clamp := func(x int) byte {
- if x > 255 {
- return 255
- }
- if x < 0 {
- return 0
- }
- return byte(x)
- }
- dstPix := make([]byte, 4*w*h)
- for i := 0; i < w; i++ {
- r, g, b, a := dstColor(i)
- dstPix[4*i] = r
- dstPix[4*i+1] = g
- dstPix[4*i+2] = b
- dstPix[4*i+3] = a
- }
- srcPix := make([]byte, 4*w*h)
- for i := 0; i < w; i++ {
- r, g, b, a := srcColor(i)
- srcPix[4*i] = r
- srcPix[4*i+1] = g
- srcPix[4*i+2] = b
- srcPix[4*i+3] = a
- }
- src.WritePixels(srcPix)
- operations := []ebiten.BlendOperation{
- ebiten.BlendOperationAdd,
- ebiten.BlendOperationSubtract,
- ebiten.BlendOperationReverseSubtract,
- }
- for _, rgbOp := range operations {
- for _, alphaOp := range operations {
- // Reset the destination state.
- dst.WritePixels(dstPix)
- op := &ebiten.DrawImageOptions{}
- op.Blend = ebiten.Blend{
- BlendFactorSourceRGB: ebiten.BlendFactorOne,
- BlendFactorSourceAlpha: ebiten.BlendFactorOne,
- BlendFactorDestinationRGB: ebiten.BlendFactorOne,
- BlendFactorDestinationAlpha: ebiten.BlendFactorOne,
- BlendOperationRGB: rgbOp,
- BlendOperationAlpha: alphaOp,
- }
- dst.DrawImage(src, op)
- for i := 0; i < w; i++ {
- got := dst.At(i, 0).(color.RGBA)
- sr, sg, sb, sa := srcColor(i)
- dr, dg, db, da := dstColor(i)
- var want color.RGBA
- switch rgbOp {
- case ebiten.BlendOperationAdd:
- want.R = clamp(int(sr) + int(dr))
- want.G = clamp(int(sg) + int(dg))
- want.B = clamp(int(sb) + int(db))
- case ebiten.BlendOperationSubtract:
- want.R = clamp(int(sr) - int(dr))
- want.G = clamp(int(sg) - int(dg))
- want.B = clamp(int(sb) - int(db))
- case ebiten.BlendOperationReverseSubtract:
- want.R = clamp(int(dr) - int(sr))
- want.G = clamp(int(dg) - int(sg))
- want.B = clamp(int(db) - int(sb))
- }
- switch alphaOp {
- case ebiten.BlendOperationAdd:
- want.A = clamp(int(sa) + int(da))
- case ebiten.BlendOperationSubtract:
- want.A = clamp(int(sa) - int(da))
- case ebiten.BlendOperationReverseSubtract:
- want.A = clamp(int(da) - int(sa))
- }
- if !sameColors(got, want, 1) {
- t.Errorf("dst.At(%d, 0): operations: %d, %d: got: %v, want: %v", i, rgbOp, alphaOp, got, want)
- }
- }
- }
- }
- }
- func TestImageBlendOperationMinAndMax(t *testing.T) {
- const w, h = 16, 1
- dst := ebiten.NewImage(w, h)
- src := ebiten.NewImage(w, h)
- dstColor := func(i int) (byte, byte, byte, byte) {
- return byte(4 * i * 17), byte(4*i*17 + 1), byte(4*i*17 + 2), byte(4*i*17 + 3)
- }
- srcColor := func(i int) (byte, byte, byte, byte) {
- return byte(4 * i * 13), byte(4*i*13 + 1), byte(4*i*13 + 2), byte(4*i*13 + 3)
- }
- dstPix := make([]byte, 4*w*h)
- for i := 0; i < w; i++ {
- r, g, b, a := dstColor(i)
- dstPix[4*i] = r
- dstPix[4*i+1] = g
- dstPix[4*i+2] = b
- dstPix[4*i+3] = a
- }
- srcPix := make([]byte, 4*w*h)
- for i := 0; i < w; i++ {
- r, g, b, a := srcColor(i)
- srcPix[4*i] = r
- srcPix[4*i+1] = g
- srcPix[4*i+2] = b
- srcPix[4*i+3] = a
- }
- src.WritePixels(srcPix)
- operations := []ebiten.BlendOperation{
- ebiten.BlendOperationMin,
- ebiten.BlendOperationMax,
- }
- for _, rgbOp := range operations {
- for _, alphaOp := range operations {
- // Reset the destination state.
- dst.WritePixels(dstPix)
- op := &ebiten.DrawImageOptions{}
- // Use the default blend factors, and confirm that the factors are ignored.
- op.Blend = ebiten.Blend{
- BlendFactorSourceRGB: ebiten.BlendFactorDefault,
- BlendFactorSourceAlpha: ebiten.BlendFactorDefault,
- BlendFactorDestinationRGB: ebiten.BlendFactorDefault,
- BlendFactorDestinationAlpha: ebiten.BlendFactorDefault,
- BlendOperationRGB: rgbOp,
- BlendOperationAlpha: alphaOp,
- }
- dst.DrawImage(src, op)
- for i := 0; i < w; i++ {
- got := dst.At(i, 0).(color.RGBA)
- sr, sg, sb, sa := srcColor(i)
- dr, dg, db, da := dstColor(i)
- var want color.RGBA
- switch rgbOp {
- case ebiten.BlendOperationMin:
- want.R = min(sr, dr)
- want.G = min(sg, dg)
- want.B = min(sb, db)
- case ebiten.BlendOperationMax:
- want.R = max(sr, dr)
- want.G = max(sg, dg)
- want.B = max(sb, db)
- }
- switch alphaOp {
- case ebiten.BlendOperationMin:
- want.A = min(sa, da)
- case ebiten.BlendOperationMax:
- want.A = max(sa, da)
- }
- if !sameColors(got, want, 1) {
- t.Errorf("dst.At(%d, 0): operations: %d, %d: got: %v, want: %v", i, rgbOp, alphaOp, got, want)
- }
- }
- }
- }
- }
- func TestImageBlendFactor(t *testing.T) {
- if skipTooSlowTests(t) {
- return
- }
- const w, h = 16, 1
- dst := ebiten.NewImage(w, h)
- src := ebiten.NewImage(w, h)
- dstColor := func(i int) (byte, byte, byte, byte) {
- return byte(4 * i * 17), byte(4*i*17 + 1), byte(4*i*17 + 2), byte(4*i*17 + 3)
- }
- srcColor := func(i int) (byte, byte, byte, byte) {
- return byte(4 * i * 13), byte(4*i*13 + 1), byte(4*i*13 + 2), byte(4*i*13 + 3)
- }
- colorToFloats := func(r, g, b, a byte) (float64, float64, float64, float64) {
- return float64(r) / 0xff, float64(g) / 0xff, float64(b) / 0xff, float64(a) / 0xff
- }
- clamp := func(x int) byte {
- if x > 255 {
- return 255
- }
- if x < 0 {
- return 0
- }
- return byte(x)
- }
- dstPix := make([]byte, 4*w*h)
- for i := 0; i < w; i++ {
- r, g, b, a := dstColor(i)
- dstPix[4*i] = r
- dstPix[4*i+1] = g
- dstPix[4*i+2] = b
- dstPix[4*i+3] = a
- }
- srcPix := make([]byte, 4*w*h)
- for i := 0; i < w; i++ {
- r, g, b, a := srcColor(i)
- srcPix[4*i] = r
- srcPix[4*i+1] = g
- srcPix[4*i+2] = b
- srcPix[4*i+3] = a
- }
- src.WritePixels(srcPix)
- factors := []ebiten.BlendFactor{
- ebiten.BlendFactorZero,
- ebiten.BlendFactorOne,
- ebiten.BlendFactorSourceColor,
- ebiten.BlendFactorOneMinusSourceColor,
- ebiten.BlendFactorSourceAlpha,
- ebiten.BlendFactorOneMinusSourceAlpha,
- ebiten.BlendFactorDestinationColor,
- ebiten.BlendFactorOneMinusDestinationColor,
- ebiten.BlendFactorDestinationAlpha,
- ebiten.BlendFactorOneMinusDestinationAlpha,
- }
- for _, srcRGBFactor := range factors {
- for _, srcAlphaFactor := range factors {
- for _, dstRGBFactor := range factors {
- for _, dstAlphaFactor := range factors {
- // Reset the destination state.
- dst.WritePixels(dstPix)
- op := &ebiten.DrawImageOptions{}
- op.Blend = ebiten.Blend{
- BlendFactorSourceRGB: srcRGBFactor,
- BlendFactorSourceAlpha: srcAlphaFactor,
- BlendFactorDestinationRGB: dstRGBFactor,
- BlendFactorDestinationAlpha: dstAlphaFactor,
- BlendOperationRGB: ebiten.BlendOperationAdd,
- BlendOperationAlpha: ebiten.BlendOperationAdd,
- }
- dst.DrawImage(src, op)
- for i := 0; i < w; i++ {
- got := dst.At(i, 0).(color.RGBA)
- sr, sg, sb, sa := colorToFloats(srcColor(i))
- dr, dg, db, da := colorToFloats(dstColor(i))
- var r, g, b, a float64
- switch srcRGBFactor {
- case ebiten.BlendFactorZero:
- r += 0 * sr
- g += 0 * sg
- b += 0 * sb
- case ebiten.BlendFactorOne:
- r += 1 * sr
- g += 1 * sg
- b += 1 * sb
- case ebiten.BlendFactorSourceColor:
- r += sr * sr
- g += sg * sg
- b += sb * sb
- case ebiten.BlendFactorOneMinusSourceColor:
- r += (1 - sr) * sr
- g += (1 - sg) * sg
- b += (1 - sb) * sb
- case ebiten.BlendFactorSourceAlpha:
- r += sa * sr
- g += sa * sg
- b += sa * sb
- case ebiten.BlendFactorOneMinusSourceAlpha:
- r += (1 - sa) * sr
- g += (1 - sa) * sg
- b += (1 - sa) * sb
- case ebiten.BlendFactorDestinationColor:
- r += dr * sr
- g += dg * sg
- b += db * sb
- case ebiten.BlendFactorOneMinusDestinationColor:
- r += (1 - dr) * sr
- g += (1 - dg) * sg
- b += (1 - db) * sb
- case ebiten.BlendFactorDestinationAlpha:
- r += da * sr
- g += da * sg
- b += da * sb
- case ebiten.BlendFactorOneMinusDestinationAlpha:
- r += (1 - da) * sr
- g += (1 - da) * sg
- b += (1 - da) * sb
- }
- switch srcAlphaFactor {
- case ebiten.BlendFactorZero:
- a += 0 * sa
- case ebiten.BlendFactorOne:
- a += 1 * sa
- case ebiten.BlendFactorSourceColor, ebiten.BlendFactorSourceAlpha:
- a += sa * sa
- case ebiten.BlendFactorOneMinusSourceColor, ebiten.BlendFactorOneMinusSourceAlpha:
- a += (1 - sa) * sa
- case ebiten.BlendFactorDestinationColor, ebiten.BlendFactorDestinationAlpha:
- a += da * sa
- case ebiten.BlendFactorOneMinusDestinationColor, ebiten.BlendFactorOneMinusDestinationAlpha:
- a += (1 - da) * sa
- }
- switch dstRGBFactor {
- case ebiten.BlendFactorZero:
- r += 0 * dr
- g += 0 * dg
- b += 0 * db
- case ebiten.BlendFactorOne:
- r += 1 * dr
- g += 1 * dg
- b += 1 * db
- case ebiten.BlendFactorSourceColor:
- r += sr * dr
- g += sg * dg
- b += sb * db
- case ebiten.BlendFactorOneMinusSourceColor:
- r += (1 - sr) * dr
- g += (1 - sg) * dg
- b += (1 - sb) * db
- case ebiten.BlendFactorSourceAlpha:
- r += sa * dr
- g += sa * dg
- b += sa * db
- case ebiten.BlendFactorOneMinusSourceAlpha:
- r += (1 - sa) * dr
- g += (1 - sa) * dg
- b += (1 - sa) * db
- case ebiten.BlendFactorDestinationColor:
- r += dr * dr
- g += dg * dg
- b += db * db
- case ebiten.BlendFactorOneMinusDestinationColor:
- r += (1 - dr) * dr
- g += (1 - dg) * dg
- b += (1 - db) * db
- case ebiten.BlendFactorDestinationAlpha:
- r += da * dr
- g += da * dg
- b += da * db
- case ebiten.BlendFactorOneMinusDestinationAlpha:
- r += (1 - da) * dr
- g += (1 - da) * dg
- b += (1 - da) * db
- }
- switch dstAlphaFactor {
- case ebiten.BlendFactorZero:
- a += 0 * da
- case ebiten.BlendFactorOne:
- a += 1 * da
- case ebiten.BlendFactorSourceColor, ebiten.BlendFactorSourceAlpha:
- a += sa * da
- case ebiten.BlendFactorOneMinusSourceColor, ebiten.BlendFactorOneMinusSourceAlpha:
- a += (1 - sa) * da
- case ebiten.BlendFactorDestinationColor, ebiten.BlendFactorDestinationAlpha:
- a += da * da
- case ebiten.BlendFactorOneMinusDestinationColor, ebiten.BlendFactorOneMinusDestinationAlpha:
- a += (1 - da) * da
- }
- want := color.RGBA{
- R: clamp(int(r * 0xff)),
- G: clamp(int(g * 0xff)),
- B: clamp(int(b * 0xff)),
- A: clamp(int(a * 0xff)),
- }
- if !sameColors(got, want, 1) {
- t.Errorf("dst.At(%d, 0): factors: %d, %d, %d, %d: got: %v, want: %v", i, srcRGBFactor, srcAlphaFactor, dstRGBFactor, dstAlphaFactor, got, want)
- }
- }
- }
- }
- }
- }
- }
- func TestImageAntiAlias(t *testing.T) {
- // This value depends on internal/ui.bigOffscreenScale. Sync this.
- const bigOffscreenScale = 2
- const w, h = 272, 208
- dst0 := ebiten.NewImage(w, h)
- dst1 := ebiten.NewImage(w, h)
- tmp := ebiten.NewImage(w*bigOffscreenScale, h*bigOffscreenScale)
- src := ebiten.NewImage(3, 3)
- src.Fill(color.RGBA{R: 0x24, G: 0x3f, B: 0x6a, A: 0x88})
- for _, blend := range []ebiten.Blend{
- {}, // Default
- ebiten.BlendClear,
- ebiten.BlendCopy,
- ebiten.BlendSourceOver,
- ebiten.BlendDestinationOver,
- ebiten.BlendXor,
- ebiten.BlendLighter,
- } {
- rnd := rand.New(rand.NewPCG(0, 0))
- max := func(x, y, z byte) byte {
- if x >= y && x >= z {
- return x
- }
- if y >= x && y >= z {
- return y
- }
- return z
- }
- dstPix := make([]byte, 4*w*h)
- for i := 0; i < w*h; i++ {
- n := rnd.Int()
- r, g, b := byte(n), byte(n>>8), byte(n>>16)
- a := max(r, g, b)
- dstPix[4*i] = r
- dstPix[4*i+1] = g
- dstPix[4*i+2] = b
- dstPix[4*i+3] = a
- }
- dst0.WritePixels(dstPix)
- dst1.WritePixels(dstPix)
- tmp.Clear()
- // Create an actual result.
- op := &ebiten.DrawTrianglesOptions{}
- op.Blend = blend
- op.AntiAlias = true
- vs0 := []ebiten.Vertex{
- {
- DstX: w / 4,
- DstY: h / 4,
- SrcX: 1,
- SrcY: 1,
- ColorR: 1,
- ColorG: 1,
- ColorB: 1,
- ColorA: 1,
- },
- {
- DstX: 2 * w / 4,
- DstY: h / 4,
- SrcX: 2,
- SrcY: 1,
- ColorR: 1,
- ColorG: 1,
- ColorB: 1,
- ColorA: 1,
- },
- {
- DstX: w / 4,
- DstY: 2 * h / 4,
- SrcX: 1,
- SrcY: 2,
- ColorR: 1,
- ColorG: 1,
- ColorB: 1,
- ColorA: 1,
- },
- }
- is := []uint16{0, 1, 2}
- dst0.DrawTriangles(vs0, is, src, op)
- vs1 := []ebiten.Vertex{
- {
- DstX: 2 * w / 4,
- DstY: 3 * h / 4,
- SrcX: 1,
- SrcY: 2,
- ColorR: 1,
- ColorG: 1,
- ColorB: 1,
- ColorA: 1,
- },
- {
- DstX: 3 * w / 4,
- DstY: 2 * h / 4,
- SrcX: 2,
- SrcY: 1,
- ColorR: 1,
- ColorG: 1,
- ColorB: 1,
- ColorA: 1,
- },
- {
- DstX: 3 * w / 4,
- DstY: 3 * h / 4,
- SrcX: 2,
- SrcY: 2,
- ColorR: 1,
- ColorG: 1,
- ColorB: 1,
- ColorA: 1,
- },
- }
- dst0.DrawTriangles(vs1, is, src, op)
- // Create an expected result.
- // Copy an enlarged destination image to the offscreen.
- opCopy := &ebiten.DrawImageOptions{}
- opCopy.GeoM.Scale(bigOffscreenScale, bigOffscreenScale)
- opCopy.Blend = ebiten.BlendCopy
- tmp.DrawImage(dst1, opCopy)
- // Render the vertices onto the offscreen.
- for i := range vs0 {
- vs0[i].DstX *= 2
- vs0[i].DstY *= 2
- }
- for i := range vs1 {
- vs1[i].DstX *= 2
- vs1[i].DstY *= 2
- }
- op = &ebiten.DrawTrianglesOptions{}
- op.Blend = blend
- tmp.DrawTriangles(vs0, is, src, op)
- tmp.DrawTriangles(vs1, is, src, op)
- // Render a shrunk offscreen image onto the destination.
- opShrink := &ebiten.DrawImageOptions{}
- opShrink.GeoM.Scale(1.0/bigOffscreenScale, 1.0/bigOffscreenScale)
- opShrink.Filter = ebiten.FilterLinear
- opShrink.Blend = ebiten.BlendCopy
- dst1.DrawImage(tmp, opShrink)
- for j := 0; j < h; j++ {
- for i := 0; i < w; i++ {
- got := dst0.At(i, j).(color.RGBA)
- want := dst1.At(i, j).(color.RGBA)
- if !sameColors(got, want, 2) {
- t.Errorf("At(%d, %d), blend: %v, got: %v, want: %v", i, j, blend, got, want)
- }
- }
- }
- }
- }
- func TestImageColorMScale(t *testing.T) {
- const w, h = 16, 16
- dst0 := ebiten.NewImage(w, h)
- dst1 := ebiten.NewImage(w, h)
- src := ebiten.NewImage(w, h)
- src.Fill(color.RGBA{R: 0x24, G: 0x3f, B: 0x6a, A: 0x88})
- // As the ColorM is a diagonal matrix, a built-in shader for a color matrix is NOT used.
- op := &ebiten.DrawImageOptions{}
- op.ColorM.Scale(0.3, 0.4, 0.5, 0.6)
- dst0.DrawImage(src, op)
- // As the ColorM is not a diagonal matrix, a built-in shader for a color matrix is used.
- op = &ebiten.DrawImageOptions{}
- op.ColorM.Scale(0.3, 0.4, 0.5, 0.6)
- op.ColorM.Translate(0, 0, 0, 1e-4)
- dst1.DrawImage(src, op)
- got := dst0.At(0, 0)
- want := dst1.At(0, 0)
- if got != want {
- t.Errorf("got: %v, want: %v", got, want)
- }
- }
- func TestImageColorScaleAndColorM(t *testing.T) {
- const w, h = 16, 16
- dst0 := ebiten.NewImage(w, h)
- dst1 := ebiten.NewImage(w, h)
- src := ebiten.NewImage(w, h)
- src.Fill(color.RGBA{R: 0x24, G: 0x3f, B: 0x6a, A: 0x88})
- // ColorScale is applied to premultiplied-alpha colors.
- op := &ebiten.DrawImageOptions{}
- op.ColorScale.Scale(0.3*0.6, 0.4*0.6, 0.5*0.6, 0.6)
- dst0.DrawImage(src, op)
- // ColorM.Scale is applied to straight-alpha colors.
- op = &ebiten.DrawImageOptions{}
- op.ColorM.Scale(0.3, 0.4, 0.5, 0.6)
- dst1.DrawImage(src, op)
- got := dst0.At(0, 0)
- want := dst1.At(0, 0)
- if got != want {
- t.Errorf("got: %v, want: %v", got, want)
- }
- }
- // Issue #2428
- func TestImageSetAndSubImage(t *testing.T) {
- const w, h = 16, 16
- img := ebiten.NewImage(w, h)
- img.Set(1, 1, color.RGBA{R: 0xff, A: 0xff})
- got := img.SubImage(image.Rect(0, 0, w, h)).At(1, 1).(color.RGBA)
- want := color.RGBA{R: 0xff, A: 0xff}
- if got != want {
- t.Errorf("got: %v, want: %v", got, want)
- }
- }
- // Issue #2611
- func TestImageDrawTrianglesWithGreaterIndexThanVerticesCount(t *testing.T) {
- defer func() {
- if r := recover(); r == nil {
- t.Errorf("DrawTriangles must panic but not")
- }
- }()
- const w, h = 16, 16
- dst := ebiten.NewImage(w, h)
- src := ebiten.NewImage(w, h)
- vs := make([]ebiten.Vertex, 4)
- is := []uint16{0, 1, 2, 1, 2, 4}
- dst.DrawTriangles(vs, is, src, nil)
- }
- // Issue #2611
- func TestImageDrawTrianglesShaderWithGreaterIndexThanVerticesCount(t *testing.T) {
- defer func() {
- if r := recover(); r == nil {
- t.Errorf("DrawTrianglesShader must panic but not")
- }
- }()
- const w, h = 16, 16
- dst := ebiten.NewImage(w, h)
- vs := make([]ebiten.Vertex, 4)
- is := []uint16{0, 1, 2, 1, 2, 4}
- shader, err := ebiten.NewShader([]byte(`
- package main
- func Fragment(dstPos vec4, srcPos vec2, color vec4) vec4 {
- return color
- }
- `))
- if err != nil {
- t.Fatalf("could not compile shader: %v", err)
- }
- dst.DrawTrianglesShader(vs, is, shader, nil)
- }
- // Issue #2733
- func TestImageGeoMAfterDraw(t *testing.T) {
- src := ebiten.NewImage(1, 1)
- dst := ebiten.NewImageWithOptions(image.Rect(-1, -1, 0, 0), nil)
- op0 := &ebiten.DrawImageOptions{}
- dst.DrawImage(src, op0)
- if x, y := op0.GeoM.Apply(0, 0); x != 0 || y != 0 {
- t.Errorf("got: (%0.2f, %0.2f), want: (0, 0)", x, y)
- }
- s, err := ebiten.NewShader([]byte(`//kage:unit pixels
- package main
- func Fragment(dstPos vec4, srcPos vec2, color vec4) vec4 {
- return vec4(1)
- }
- `))
- if err != nil {
- t.Fatal(err)
- }
- op1 := &ebiten.DrawRectShaderOptions{}
- dst.DrawRectShader(1, 1, s, op1)
- if x, y := op1.GeoM.Apply(0, 0); x != 0 || y != 0 {
- t.Errorf("got: (%0.2f, %0.2f), want: (0, 0)", x, y)
- }
- }
- func TestImageWritePixelAndDispose(t *testing.T) {
- const (
- w = 16
- h = 16
- )
- img := ebiten.NewImage(w, h)
- pix := make([]byte, 4*w*h)
- for i := range pix {
- pix[i] = 0xff
- }
- img.WritePixels(pix)
- img.Dispose()
- // Confirm that any pixel information is invalidated after Dispose is called.
- if got, want := img.At(0, 0), (color.RGBA{}); got != want {
- t.Errorf("got: %v, want: %v", got, want)
- }
- }
- func TestImageWritePixelAndDeallocate(t *testing.T) {
- const (
- w = 16
- h = 16
- )
- img := ebiten.NewImage(w, h)
- pix := make([]byte, 4*w*h)
- for i := range pix {
- pix[i] = 0xff
- }
- img.WritePixels(pix)
- img.Deallocate()
- // Confirm that any pixel information is cleared after Deallocate is called.
- if got, want := img.At(0, 0), (color.RGBA{}); got != want {
- t.Errorf("got: %v, want: %v", got, want)
- }
- }
- func TestImageDrawImageAfterDeallocation(t *testing.T) {
- src, _, err := openEbitenImage()
- if err != nil {
- t.Fatal(err)
- return
- }
- w, h := src.Bounds().Dx(), src.Bounds().Dy()
- dst := ebiten.NewImage(w, h)
- dst.DrawImage(src, nil)
- for j := 0; j < h; j++ {
- for i := 0; i < w; i++ {
- got := dst.At(i, j)
- want := src.At(i, j)
- if got != want {
- t.Errorf("At(%d, %d): got: %v, want: %v", i, j, got, want)
- }
- }
- }
- // Even after deallocating the image, the image is still available.
- dst.Deallocate()
- dst.DrawImage(src, nil)
- for j := 0; j < h; j++ {
- for i := 0; i < w; i++ {
- got := dst.At(i, j)
- want := src.At(i, j)
- if got != want {
- t.Errorf("At(%d, %d): got: %v, want: %v", i, j, got, want)
- }
- }
- }
- }
- // Issue #2798
- func TestImageInvalidPremultipliedAlphaColor(t *testing.T) {
- // This test checks the rendering result when Set and WritePixels use an invalid premultiplied alpha color.
- // The result values are kept and not clamped.
- const (
- w = 16
- h = 16
- )
- dst := ebiten.NewImage(w, h)
- dst.Set(0, 0, color.RGBA{R: 0xff, G: 0xc0, B: 0x80, A: 0x40})
- dst.Set(0, 1, color.RGBA{R: 0xff, G: 0xc0, B: 0x80, A: 0x00})
- if got, want := dst.At(0, 0).(color.RGBA), (color.RGBA{R: 0xff, G: 0xc0, B: 0x80, A: 0x40}); got != want {
- t.Errorf("got: %v, want: %v", got, want)
- }
- if got, want := dst.At(0, 1).(color.RGBA), (color.RGBA{R: 0xff, G: 0xc0, B: 0x80, A: 0x00}); got != want {
- t.Errorf("got: %v, want: %v", got, want)
- }
- pix := make([]byte, 4*w*h)
- for j := 0; j < h; j++ {
- for i := 0; i < w; i++ {
- pix[4*(j*16+i)] = byte(i)
- pix[4*(j*16+i)+1] = byte(j)
- pix[4*(j*16+i)+2] = 0x80
- pix[4*(j*16+i)+3] = byte(i - j)
- }
- }
- dst.WritePixels(pix)
- for j := 0; j < h; j++ {
- for i := 0; i < w; i++ {
- got := dst.At(i, j)
- want := color.RGBA{R: byte(i), G: byte(j), B: 0x80, A: byte(i - j)}
- if got != want {
- t.Errorf("At(%d, %d): got: %v, want: %v", i, j, got, want)
- }
- }
- }
- }
|