Professional Documents
Culture Documents
AndreasAgvard
WorkingintheSonyEricssonsoftwaredepartment,Ioftencomeacrossapplicationswhereimage scalingisneeded,forexamplewhenhandlingimagesfromexternalsourcessuchascontentprovidersor theweb.Scalingisneededsincetheimageyouwishtopresentusuallydoesntfitthewayyouwishto presenttheimage. ThisistypicalifyouaredevelopingaLiveViewextensionforyourapplication.Mostthepeople developingapplicationsutilisingLiveViewandothersecondscreendevices,probablywillneedto rescaleimages,whereitwillbeimportanttomaintainaproperratioandimagequality.Thisisofcourse applicableinalotothercasesaswell.Rescalingimagescanbeabitdifficulttodoinaneffectiveway. ImageViewsolvesmanyscalingproblems,atleastaslongasyoucansetanimagesourcedirectly withoutdecodingorscalingtheimageyourselffirst.Butsometimesyouneedtotakecontrolofthe decodingyourself,andthatiswherethistutorialcomesin.Alongwiththistutorial,Ivewrittenacode sampleproject.Downloadthecodeexampleprojecttolearnmore.Theresultspresentedinthistextcan beachievedbycompilingandrunningthatproject. Isolatingtheproblem IvemadethistutorialbecauseIveimplementedanumberofusefulutilitymethodsfordoingscalingin awaythatavoidsthemostcommonimagescalingpitfalls,suchasthenaveexamplebelow:
Bitmap unscaledBitmap = BitmapFactory.decodeResource(getResources(), mSourceId); Bitmap scaledBitmap = Bitmap.createScaledBitmap(unscaledBitmap, wantedWidth, wantedHeight, true);
Sowhatisdoneandwhatiswronginthecodeabovethen?Letslookatthedifferentlinesofcode. Line1:Theentiresourceimageisdecodedtoabitmap. Thismightcauseanoutofmemoryerroriftheimageistoolarge. Thismightresultinadecodedimagewithahigherresolutionthanrequired.Itmightalsobe unnecessarilyslowassmartdecoderscanscalewhendecodingatimprovedperformance. Scalinganimagealot,aswhenscalingahighresolutionbitmaptoalowresolution,causes aliasingproblems.Usingbitmapfiltering(forexample,passingtrueasthelatterparameterto Bitmap.createScaledBitmap())reducesthealiasingbutisnotenoughwhenalotofscalingis applied. Line2:Thedecodedbitmapisscaledtothewantedsize. Theaspectratioofthesourceimagedimensionsandthewantedimagedimensionsmaynotbe thesame.Thiswillresultinastretchedimage.
Leftimage:Originalimage.Rightimage:Imagescaledto250by250pixelswithanavemethod.Aliasingproblems canbeseensuchasoneeyehavingasharphighlightandtheotherhavingnone.Stretchingoccursontheheight.
Creatingasolution Oursolutionwillhaveastructuresimilartothecodeabovewithwhereonepartwillreplaceline1, wherewedecodeanimageinpreparationforscaling.Anotherpartwillbetoreplaceline2,anddothe finalscaling.We'llstartwiththepartreplacingofline2asitwillintroducetwonewconcepts,cropand fit,whichwillimpactthesolutionforreplacingline1aswell. Replacingline2 Inthispart,wearescalingthebitmapaccordingtoourneeds.Thisstepisnecessarysincethedecoding linethatprecedesthiswillhavelimitedcapabilitiestoscale.Alsointhisstep,wemighthavetoadjust thewantedsizeofourimageifwewishtoavoidstretching. Toavoidstretching,therearetwopossibilities.Eitherweadjustthewanteddimensionsbymakingsure theyhavethesameaspectratioasthesourceimage,i.e.scalingthesourceimageuntilitfitswithinthe wanteddimensions,orwecropthesourceimagewithanareathathasthesameaspectratioasthe wanteddimensions.
Inordertoscalelikethis,weimplementthefollowingmethod:
public static Bitmap createScaledBitmap(Bitmap unscaledBitmap, int dstWidth, int dstHeight, ScalingLogic scalingLogic) { Rect srcRect = calculateSrcRect(unscaledBitmap.getWidth(), unscaledBitmap.getHeight(), dstWidth, dstHeight, scalingLogic); Rect dstRect = calculateDstRect(unscaledBitmap.getWidth(), unscaledBitmap.getHeight(), dstWidth, dstHeight, scalingLogic); Bitmap scaledBitmap = Bitmap.createBitmap(dstRect.width(), dstRect.height(), Config.ARGB_8888); Canvas canvas = new Canvas(scaledBitmap); canvas.drawBitmap(unscaledBitmap, srcRect, dstRect, new Paint(Paint.FILTER_BITMAP_FLAG)); return scaledBitmap; }
Replacingline1 Decodersaresmart,especiallytheonesusedfortheJPEGandPNGformats.Thesedecoderscanscale theimagewhendecoding,withimprovedperformance.Whendoingso,aliasingproblemsarealso avoided.Also,sincetheimageissmallerafterdecoding,lessmemorywillbeneeded. ScalingwhendecodingisassimpleassettingtheinSampleSizeparameteronaBitmapFactory.Options objectandpassingittotheBitmapFactorywhendecoding.Thesamplesizespecifiesafactorofwhich eachsideoftheimageisscaled,forexampleafactorof2ona640x480imagewillresultina320x240 imagebeingdecoded.Whensettingasamplesize,youarenotguaranteedtheimagewillbescaled downexactlyaccordingtothatnumber,butatleastitwillneverbesmaller.Forexample,afactorof3 ona640x480imagecouldresultina320x240image,sincethevalue3mightnotbesupported. Commonly,atleastthefirstpowersof2aresupported[1,2,4,8]. Thenextstepistospecifyapropersamplesize.Thepropersamplesizewouldbetheoneresultingin thelargestamountofscaling,butstillbeingequaltoorlargerthanthewantedimagedimensions.Thisis implementedlikethis:
public static Bitmap decodeFile(String pathName, int dstWidth, int dstHeight, ScalingLogic scalingLogic) { Options options = new Options(); options.inJustDecodeBounds = true; BitmapFactory.decodeFile(pathName, options); options.inJustDecodeBounds = false; options.inSampleSize = calculateSampleSize(options.outWidth, options.outHeight, dstWidth, dstHeight, scalingLogic); Bitmap unscaledBitmap = BitmapFactory.decodeFile(pathName, options); return unscaledBitmap; } public static int calculateSampleSize(int srcWidth, int srcHeight, int dstWidth, int dstHeight, ScalingLogic scalingLogic) { if (scalingLogic == ScalingLogic.FIT) { final float srcAspect = (float)srcWidth / (float)srcHeight; final float dstAspect = (float)dstWidth / (float)dstHeight; if (srcAspect > dstAspect) { return srcWidth / dstWidth; } else { return srcHeight / dstHeight; } } else { final float srcAspect = (float)srcWidth / (float)srcHeight; final float dstAspect = (float)dstWidth / (float)dstHeight; if (srcAspect > dstAspect) { return srcHeight / dstHeight; } else { return srcWidth / dstWidth; } } }