Sie sind auf Seite 1von 6

How to scale images for your Android application

Hardtogetimagesscaledcorrectlyforyourapplication?Areyourimagestoolargeandcausingmemory problems?Oraretheyscaledincorrectlywithapooruserexperienceasaresult?Tofindagoodsolution forthis,weaskedAndreasAgvardfromtheSonyEricssonsoftwaredepartmenttohelpshedsomelight onthistopic.

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.

Leftimage:Imagescaledto250by250pixelswiththefitmethod.Imagehasbeenscaledtofitwithinthewanted dimensionsandasaresulttheheightoftheimageissmallerthanthewantedheight.Rightimage:Imagescaledto 250by250pixelswithcropmethod.Imagehasbeenscaledtofitatleastoneofthewanteddimensionsandasa resultthesourcehasbeencropped,cuttingawaytheleftandrightpartsofthesourceimage.

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; }

Inthecodeabove,weusecanvas.drawBitmap()todothescaling.Thismethodcropstheareaspecified bythesourcerectanglefromthesourceimageandscalesittoanareainthecanvasdefinedbythe destinationrectangle.Inordertoavoidstretching,thesetworectanglesneedtohavethesameaspect ratio.Wealsocalltwoutilitymethods,oneforcreatingthesourcerectangleandanotherforcreating thedestinationrectangle.Theseareimplementedlikethis:


public static Rect calculateSrcRect(int srcWidth, int srcHeight, int dstWidth, int dstHeight, ScalingLogic scalingLogic) { if (scalingLogic == ScalingLogic.CROP) { final float srcAspect = (float)srcWidth / (float)srcHeight; final float dstAspect = (float)dstWidth / (float)dstHeight; if (srcAspect > dstAspect) { final int srcRectWidth = (int)(srcHeight * dstAspect); final int srcRectLeft = (srcWidth - srcRectWidth) / 2; return new Rect(srcRectLeft, 0, srcRectLeft + srcRectWidth, srcHeight); } else { final int srcRectHeight = (int)(srcWidth / dstAspect); final int scrRectTop = (int)(srcHeight - srcRectHeight) / 2; return new Rect(0, scrRectTop, srcWidth, scrRectTop + srcRectHeight); } } else { return new Rect(0, 0, srcWidth, srcHeight); } } public static Rect calculateDstRect(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 new Rect(0, 0, dstWidth, (int)(dstWidth / srcAspect)); } else { return new Rect(0, 0, (int)(dstHeight * srcAspect), dstHeight); } } else { return new Rect(0, 0, dstWidth, dstHeight); } }

Thesourcerectanglewillbetheentiresourcedimensioninthefitcase.Inthecropcase,itiscalculated tohavethesameaspectratioasthedestinationimage,resultingeitherinthewidthortheheightofthe sourceimagebeingcropped.Thedestinationrectanglewillbetheentirewanteddimensioninthecrop case.Inthefitcase,itwillhavethesameaspectratioasthesourceimage,resultingineitherthewidth ortheheightofthewanteddimensionsbeingadjusted.

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; } } }

InthedecodeFile()method,wedecodeafileoptimizedforthefinaldownscaling.Thisisdonebyfirst decodingonlythedimensionsofthesourceimage,thencalculatingtheoptimalsamplesizeusing calculateSampleSize(),andfinallydecodingtheimageusingthissamplesize.Illleaveituptoyouif youdliketodigdeeperintounderstandingthecalculateSampleSize()method.Butbasicallyitmakes suretheimageisscaledasmuchaspossiblewhilestillbeingequalto,orlarger,thanthesource rectanglethatwasappliedbefore.

Puttingitalltogether Withthehelpfromtheutilitymethodsspecifiedabovewecannowimplementthefollowing replacementlinesfortheinitialcodepresented:


Bitmap unscaledBitmap = decodeFile(pathname, dstWidth, dstHeight, scalingLogic); Bitmap scaledBitmap = createScaledBitmap(unscaledBitmap, dstWidth, dstHeight, scalingLogic);

Leftimage:Donebyanavesolutiononmdpidevice,decodingconsumed6693kbofmemoryandtookabout1/4 second.Theresultisstretchedandsuffersfromaliasingartifacts.Middleimage:Achivedbythefitsolutionon mdpidevice,decodingconsumed418kbofmemoryandtookabout1/10second.Rightimage:Achivedbythe cropsolutiononmdpidevice,decodingconsumed418kbofmemoryandtookabout1/10second.

Tolearnmore,downloadourcodesampleproject.Withthisproject,youcanseetheresultsonyour Androidphoneandfollowtheflowinthesourcecode. AndreasAgvard SonyEricssonSoftwaredepartment

Das könnte Ihnen auch gefallen