text:
<TITLE>Review for Sister My Sister (1994)</TITLE>
<LINK REL="STYLESHEET" TYPE="text/css" HREF="/ramr.css">

Sister My Sister (1994)

reviewed by
James Berardinelli

                               MY SISTER MY SISTER
                       A film review by James Berardinelli
                        Copyright 1995 James Berardinelli

RATING (0 TO 10):  7.8

United Kingdom, 1994
Running Length: 1:29
MPAA Classification: No MPAA Rating (Sex, violence, mature themes)
Theatrical Aspect Ratio: 1.85:1

Cast: Julie Walters, Joely Richardson, Jodhi May, Sophie Thursfield
Director: Nancy Meckler
Producer: Norma Heyman
Screenplay: Wendy Kesselman based on her play
Cinematography: Ashley Rowe
Music: Stephen Warbeck
No U.S. Distributor

     Because of the universally negative reaction to incest, it's a
subject rarely tackled by motion pictures -- especially when that incest
is lesbian in nature.  Therefore, in its depiction of the twisted
relationship between co-dependent sisters, SISTER MY SISTER is unique.
Like HEAVENLY CREATURES, this tale of frantic love and brutal murder is
based on a true story.  While Nancy Meckler's film lacks the
completeness of Peter Jackson's, the two movies together represent a
fascinating study of parallels in obsession-based murder.

     SISTER MY SISTER opens with a brief prologue showing sisters
Christine (Joely Richardson) and Lea (Jodhi May) as little girls.  It's
clear that the younger Lea is Mother's favorite; the parental reaction
to Christine borders on contempt.  The scene then shifts ahead some two
decades, to a small provincial town in 1932 France.  Christine, working
as a maid for Madame Danzard (Julie Walters) and her plain daughter
Isabelle (Sophie Thursfield), has managed to persuade Lea to join her in
service.  Reunited at last, and out from under their mother's control,
the two sisters rejoice.  Meanwhile, Madame Danzard finds the
arrangement quite suitable, since she gets two maids for the price of
one.

     But Christine and Lea's relationship has already been poisoned by
their mother's favoritism.  Lea, painfully shy and uncertain of herself,
needs Christine's approval for everything.  The older sister, on the
other hand, harbors a barely-concealed rage that occasionally finds form
in tempestuous explosions.  She protects her sister with a fierce
jealousy that goes beyond the rational.  As time passes, the sisters
spend almost all their free hours cloistered in their upstairs room,
hiding from a world that they no longer feel a part of.  In the process,
their relationship crosses the line from platonic to sexual.

     One of the most unusual elements of SISTER MY SISTER is that it's
peppered with comic moments.  However, rather than lightening the mood,
the humor deepens the story's uneasy tone.  Put simply, we get the
feeling that we're laughing when we shouldn't be, and that sensation is
exactly what the director is aiming for.  Intense performances by Joely
Richardson (I'LL DO ANYTHING) and Jodhi May (THE LAST OF THE MOHICANS)
possess an unsettling quality, and the entire movie leaves a lingering
impression.  Character motivation is not as detailed as in HEAVENLY
CREATURES, and certain elements of the protagonists' pasts remain
ambiguous, but there's enough to lend resonance to the climax.

     The greatest failing of this film is its flat presentation of
Madame Danzard and her daughter.  Julie Walters gives a hilarious, over-
the-top performance as a prototypical high society snob, but there's
nothing real about Madame Danzard -- she's a pure stereotype with barely
a trace of any human sensibility.  This kind of simpleminded portrayal
belongs in a different motion picture.  Meckler is somewhat ambivalent
about Isabelle, occasionally lumping her with her mother in the
caricature mold, but at other times allowing the audience to glimpse a
more evenhanded personality.

     SISTER MY SISTER is daring, and would have received more attention
had the superior HEAVENLY CREATURES not reached the screen first (both
movies went before cameras at about the same time).  The story raises a
lot of questions about where the division lies between healthy and
diseased love, and the sorts of tragic results that can come when humans
are subjected to unreasonable repression.  Although those easily
offended will find SISTER MY SISTER unbearable, even the most liberal
viewer will leave the theater affected by the naked passions laid bare
during the course of the film.

- James Berardinelli (jberardinell@delphi.com)

The review above was posted to the
rec.arts.movies.reviews newsgroup (de.rec.film.kritiken for German reviews).

The Internet Movie Database accepts no responsibility for the contents of the
review and has no editorial control. Unless stated otherwise, the copyright
belongs to the author.

Please direct comments/criticisms of the review to relevant newsgroups.

Broken URLs inthe reviews are the responsibility of the author.

The formatting of the review is likely to differ from the original due
to ASCII to HTML conversion.

Related links: index of all rec.arts.movies.reviews reviews


text:
SYNONYM

#### According to
The Catalogue of Life, 3rd January 2011

#### Published in
Bidr. Känn. Finl. Nat. Folk 37: 143 (1882)

#### Original name
Thelephora setigera Fr., 1828

### Remarks
null

redpajama_set_name: RedPajamaGithub


text:
namespace NDT {

namespace internal {

template <typename T, bool ConstQualified>
struct ConstQualifier;

template <typename T>
struct ConstQualifier<T,false> {
    typedef T type;
};

template <typename T>
struct ConstQualifier<T,true> {
    typedef const T type;
};

template <typename T>
struct ConstQualifier<T *,false> {
    typedef T * type;
};

template <typename T>
struct ConstQualifier<T *,true> {
    typedef const T * type;
};

} // namespace internal

} // namespace NDT


text:
from django.core import serializers
from django.http import HttpResponse
from django.utils import simplejson
from django.db.models import Model

def jsonify(fn, *args, **kwargs):
    """ Decorator that serializes the output of a function, most likely
    a view, as JSON, and returns the JSON in a HttpResponse.
    Inspired by Pylon's jsonify controller decorator.
    """
    def wrapper(*args, **kwargs):
        output = fn(*args, **kwargs)
        try:
            value = simplejson.dumps(output)
        except TypeError:
            json_serializer = serializers.get_serializer('json')
            serializer = json_serializer()
            try:
                serializer.serialize(output)
                value = serializer.getvalue()
            except TypeError, e:
                # Try using jsonpickle if available
                try:
                    import jsonpickle
                    value = jsonpickle.encode(output)
                except ImportError:
                    raise e

        return HttpResponse(value)
    return wrapper


text:
package com.scanvine.android.ui;

import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;

import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.DefaultHttpClient;
import org.json.JSONObject;

import android.app.Activity;
import android.os.AsyncTask;
import android.os.Bundle;
import android.support.v4.app.ListFragment;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ListView;

import com.scanvine.android.R;
import com.scanvine.android.model.Source;
import com.scanvine.android.model.Story;
import com.scanvine.android.util.Util;

/**
 * A list fragment representing a list of Stories. This fragment also supports
 * tablet devices by allowing list items to be given an 'activated' state upon
 * selection. This helps indicate which item is currently being viewed in a
 * {@link StoryDetailFragment}.
 * <p>
 * Activities containing this fragment MUST implement the {@link Callbacks} interface.
 */
public class StoryListFragment extends ListFragment {

	private static final String STATE_ACTIVATED_POSITION = "activated_position"; //only used on tables
	private int mActivatedPosition = ListView.INVALID_POSITION; //only used on tables
	private Callbacks mCallbacks = sDummyCallbacks;
	private ArrayList<Story> stories = new ArrayList<Story>();

	public interface Callbacks {
		public void onItemSelected(String json);
	    public void showProgress(Boolean progress);
	}

	// A dummy implementation for use when not attached to an activity.
	private static Callbacks sDummyCallbacks = new Callbacks() {
		@Override
		public void onItemSelected(String json) {}
	    public void showProgress(Boolean progress) {}
	};

	// Mandatory empty constructor for the fragment manager to instantiate the fragment.
	public StoryListFragment() { }

	@Override
	public void onAttach(Activity activity) {
		super.onAttach(activity);
		if (!(activity instanceof Callbacks))
			throw new IllegalStateException("Activity must implement fragment's callbacks.");
		mCallbacks = (Callbacks) activity;
	}

	@Override
	public void onDetach() {
		super.onDetach();
		mCallbacks = sDummyCallbacks;
	}

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        ListView lv = (ListView) inflater.inflate(R.layout.fragment_story_list, container, false);
        lv.setDivider(null);
        lv.setDividerHeight(0);
		return lv;

    }

    @Override
	public void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		String slug = null;
		if (getActivity().getIntent().getExtras()!=null) {
			String json = getActivity().getIntent().getExtras().getString(StoryListActivity.SOURCE);
			Source source = Source.GetSourceFor(json);
			slug = source.getSlug();
		}
		refreshList(null, null, slug);
	}

    @Override
	public void onViewCreated(View view, Bundle savedInstanceState) {
		super.onViewCreated(view, savedInstanceState);
		if (savedInstanceState != null && savedInstanceState.containsKey(STATE_ACTIVATED_POSITION))
			setActivatedPosition(savedInstanceState.getInt(STATE_ACTIVATED_POSITION));
	}

	@Override
	public void onListItemClick(ListView listView, View view, int position,
			long id) {
		super.onListItemClick(listView, view, position, id);
		mCallbacks.onItemSelected(stories.get(position).getJSON());
	}

	@Override
	public void onSaveInstanceState(Bundle outState) {
		super.onSaveInstanceState(outState);
		if (mActivatedPosition != ListView.INVALID_POSITION) {
			// Serialize and persist the activated item position.
			outState.putInt(STATE_ACTIVATED_POSITION, mActivatedPosition);
		}
	}

	private void renderStories() {
		Activity activity = getActivity();
		if (activity!=null)
			setListAdapter(new StoryArrayAdapter(activity, this, R.id.story_list, stories));
	}

	public void showDownloads() {
		new ListDownloadsTask().execute();
	}

	public void selectItem(int n) {
		String json = stories.get(n).getJSON();
		mCallbacks.onItemSelected(json);
	}

	public void refreshList(String time, String section, String source) {
		refreshList(time, section, source, false);
	}

	public void refreshListFromCache(String time, String section, String source) {
		refreshList(time, section, source, true);
	}

	private void refreshList(String time, String section, String source, boolean cached) {
		if (source!=null && "Firehose".equalsIgnoreCase(time))
			time = null;
		if (source==null && "Latest".equalsIgnoreCase(time))
			time = null;
		if (time==null && section==null && source==null)
			time = "Latest";
		new StoryFetchTask().execute(time, section, source, cached ? "cached" : null);
	}

	public void setActivateOnItemClick(boolean activateOnItemClick) {
		getListView().setChoiceMode(activateOnItemClick ? ListView.CHOICE_MODE_SINGLE : ListView.CHOICE_MODE_NONE);
	}

	private void setActivatedPosition(int position) {
		if (position == ListView.INVALID_POSITION)
			getListView().setItemChecked(mActivatedPosition, false);
		else
			getListView().setItemChecked(position, true);
		mActivatedPosition = position;
	}

	public final class StoryFetchTask extends AsyncTask<String, Boolean, String> {
		@Override
		protected String doInBackground(String... params) {
	        Log.i(""+this, "Fetching stories, params: "+Arrays.toString(params)+", online: "+Util.AreWeOnline(getActivity()));
		    publishProgress(true);
		    String result = null;
		    String time = (params==null || params.length<1 ? null : params[0]);
		    String section = (params==null || params.length<2 ? null : params[1]);
		    String source = (params==null || params.length<3 ? null : params[2]);
		    String url = "http://www.scanvine.com/api/1";

		    if (source!=null && source.length()>0)
		    	url += "/source/"+source;
		    else if (section!=null && section.length()>0)
				url += "/"+section;
		    if (time!=null && time.length()>0)
		    	url += "/"+time;

		    String fileName = Util.ConvertUrlToFilename(url);
            File storiesFile = new File(getActivity().getCacheDir(), fileName);

            if (storiesFile.exists()) {
            	boolean useCache = !Util.AreWeOnline(getActivity());
            	useCache = useCache || params.length>3 && "cached".equalsIgnoreCase(params[3]);
            	long age = System.currentTimeMillis() - storiesFile.lastModified();
            	useCache = useCache || (age>0 && age < 600*1000);
                if (useCache) {
                	Log.i(""+this, "Using cached stories file");
                	result = Util.ConvertFileToString(storiesFile);
                	return result;
                }
            }

		    HttpClient httpclient = new DefaultHttpClient();
	        Log.i(""+this, "About to fetch "+url);
		    try {
			    HttpGet httpget = new HttpGet(url);
		    	HttpResponse response = httpclient.execute(httpget);
		        Log.i(""+this, "Fetch result: "+response);
		        HttpEntity entity = response.getEntity();
		        if (entity != null) {
		            InputStream instream = entity.getContent();
		            result = Util.ConvertStreamToString(instream);
		            instream.close();
		        }
		    }
		      catch (org.apache.http.NoHttpResponseException ex) {
                if (storiesFile.exists())
                	result = Util.ConvertFileToString(storiesFile);
                return result;
		    } catch (Exception ex) {
		    	Log.e(""+this, "Fetch error: "+ex);
		    	ex.printStackTrace();
		    }

		    if (result!=null && result.length()>0) {
			    //write to cache
		        try {
		            FileWriter out = new FileWriter(storiesFile);
		            out.write(result);
		            out.close();
		            Log.i(""+this, "Cached results to "+storiesFile);
		        } catch (IOException ex) {
		            Log.e(""+this, "Write error: "+ex);
		            ex.printStackTrace();
		        }
		    }
		    return result;
		}

	    @Override
		protected void onProgressUpdate(Boolean... progress) {
		    mCallbacks.showProgress(progress[0]);
		}

		@Override
		protected void onPostExecute(String result) {
		    publishProgress(false);
		    if (result!=null && result.length()>0) {
		    	Log.i(""+this, "Got response of length "+result.length());
			    stories = Story.GetStoriesFrom(result);
		    }
		    else
		    	stories = new ArrayList<Story>();
		    renderStories();
		}
	}

	public final class ListDownloadsTask extends AsyncTask<String, Boolean, String> {
		@Override
		protected String doInBackground(String... params) {
		    publishProgress(true);
			File[] files = getActivity().getFilesDir().listFiles();
			Arrays.sort(files, new Comparator<File>() {
			    public int compare(File f1, File f2) {
			    	return Long.valueOf(f1.lastModified()).compareTo(f2.lastModified());
			    }
			});

			ArrayList<Story> downloads = new ArrayList<Story>();
			for (File child : files) {
				if (child.getName().startsWith(Story.FILE_PREFIX)) {
					Log.i(""+this, "Found "+child);
					String contents = Util.ConvertFileToString(child);
					try {
						JSONObject json = new JSONObject(contents);
						Story story = Story.GetStoryFor(json.getString("story"));
						downloads.add(story);
					}
					catch(Exception ex) {
						Log.e(""+this, "Couldn't get story from "+child);
					}
				}
			}

			stories = downloads;
		    return "done";
		}

	    @Override
		protected void onProgressUpdate(Boolean... progress) {
		    mCallbacks.showProgress(progress[0]);
		}

		@Override
		protected void onPostExecute(String result) {
		    publishProgress(false);
		    renderStories();
		}
	}
}


text:
"""
Varmint Pest - Attempts to frighten away varmints such as raccoons
Created 7/04/17 by Greg Griffes
"""
import time, sys, os
import logging

from automat import MethodicalMachine

##import RPi.GPIO as GPIO
##import pigpio # http://abyz.co.uk/rpi/pigpio/python.html
##import spidev
##import Adafruit_ADS1x15
##from collections import deque
##import threading

##import datetime
##from picamera import PiCamera

# for graphics
##from webcolors import name_to_rgb
##import pygame
##from pygame.locals import *

# Global variables
##global flow_count
##flow_count = 0

#camera = PiCamera()

# Provides a logging solution for troubleshooting
logging.basicConfig(level=logging.DEBUG, format='(%(threadName)-10s) %(message)s')

# -----------------------------------
# Constants
# -----------------------------------
NIGHT = 0
DAY = 1

# -----------------------------------
# Initialize
# -----------------------------------
# =======================================
# Local classes
# =======================================
class _daylight_sensor(object):
    # Create the daylight sensor class
    def __init__(self):
        self.night = NIGHT
        self.day = DAY

    def __del__(self):
        logging.debug('daylight sensor class closed')

    def read(self):
        logging.debug("Daylight sensor indicates night time")
        return self.night

##########################
class varmintpest_fsm(object):
##########################
    """varmintpest_fsm is a state machine using the Automat MethodicalMachine
    There are three states:
    S1: Daytime (the initial state)
    S2: Nighttime-motion (waiting for motion)
    S3: Nighttime-IR (upon IR signature, set off the varmint blast)

    State: S1 (Daytime) parameters:
        S1I1: Input 1: daylight-yes
        S1I2: Input 2: daylight-no

        S1T1: Transition 1: upon daylight-yes
            S1O1: Output 1: Play bird songs
            S1T1ns: Next state: enter daytime
        S1T2: Transition 2: upon daylight-no
            S1O2: Output 2: message entering nighttime and test for motion
            S1T2ns: Next state: enter nighttime-motion

    State: S2 (nighttime-motion) parameters:
        S2I1: Input 1: daylight-yes
        S2I2: Input 2: daylight-no
        S2I3: Input 3: motion-detected
        S2I4: Input 4: motion-not-detected

        S2T1: Transition 1: upon daylight-yes
            S2O1: message start of daytime
            S2T1ns: enter S1 daytime
        S2T2: Transition 2: upon daylight-no
            S2T2ns: enter S2 nighttime-motion
        S2T3: Transition 3: upon motion-detected
            S2O2: message motion detected
            S2T3ns: enter S3 nighttime-IR
        S2T4: Transition 4: upon motion-not-detected
            S2T4ns: enter S2 nighttime-motion

     State: S3 (Nighttime-IR) parameters:
        S3I1: Input 1: IR-detected
        S3I2: Input 2: IR-not-detected

        S3T1: Transition 1: upon IR-detected
            S3O1: Output 1: Perform Varmint-blast
            S3T1ns: Next state: enter nighttime-motion
        S3T2: Transition 2: upon IR-not-detected
            S3O2: Output 2: log false-motion-detection
            S3T2ns: Next state: enter S2 nighttime-motion

    """

# Create the state machine class from Automat
    _machine = MethodicalMachine()

#=======================================
# Inputs
#=======================================
    @_machine.input()
    def daylightYes(self):
        "Light sensor indicates day time"

    @_machine.input()
    def daylightNo(self):
        "Light sensor indicates night time"

    @_machine.input()
    def motionDetected(self):
        "Motion - possible varmint"

    @_machine.input()
    def motionNotDetected(self):
        "Nothing found, keep looking"

    @_machine.input()
    def IRDetected(self):
        "IR signature indicates varmint"

    @_machine.input()
    def IRNotDetected(self):
        "Nothing found, keep looking"

#=======================================
# Outputs
#=======================================
    @_machine.output()
    def _play_bird_sounds(self):
        logging.debug('playing bird sounds')
        time.sleep(1.0)

    @_machine.output()
    def _nighttime(self):
        logging.debug('entering nighttime-motion state')
        time.sleep(1.0)

    @_machine.output()
    def _messageStartOfDaytime(self):
        logging.debug('entering daytime state')
        time.sleep(1.0)

    @_machine.output()
    def _messageMotionDetected(self):
        logging.debug('Motion detected! Entering motion-IR state')
        time.sleep(1.0)

    @_machine.output()
    def _performVarmintBlast(self):
        logging.debug('IR detected! Performing Varmint Blast!')
        time.sleep(1.0)

    @_machine.output()
    def _messageFalseMotionDetected(self):
        logging.debug('Motion detected but no IR signature')
        time.sleep(1.0)

#=======================================
# S1 State
#=======================================
    @_machine.state(initial=True)
    def daytime(self):
        "wait for nighttime"

#=======================================
# S2 State - nighttime-motion
#=======================================
    @_machine.state()
    def nighttimeMotion(self):
        "wait for motion or daytime"

#=======================================
# S3 State - nighttime-IR
#=======================================
    @_machine.state()
    def nighttimeIR(self):
        "wait for IR signature"

#=======================================
# S1 Transition logic
#=======================================
    daytime.upon(daylightYes, enter=daytime, outputs=[_play_bird_sounds])
    daytime.upon(daylightNo, enter=nighttimeMotion, outputs=[_nighttime])

#=======================================
# S2 Transition logic
#=======================================
    nighttimeMotion.upon(daylightYes, enter=daytime, outputs=[_messageStartOfDaytime])
    nighttimeMotion.upon(daylightNo, enter=nighttimeMotion, outputs=[])
    nighttimeMotion.upon(motionDetected, enter=nighttimeIR, outputs=[_messageMotionDetected])
    nighttimeMotion.upon(motionNotDetected, enter=nighttimeMotion, outputs=[])

#=======================================
# S3 Transition logic
#=======================================
    nighttimeIR.upon(IRDetected, enter=nighttimeMotion, outputs=[_performVarmintBlast])
    nighttimeIR.upon(IRNotDetected, enter=nighttimeMotion, outputs=[_messageFalseMotionDetected])

###############################################################
# Main program
###############################################################
if __name__ == '__main__':

# -----------------------------------
# Initialize variables
# -----------------------------------

# -----------------------------------
# Exception handler
# -----------------------------------
    try:

        logging.debug('debug logging is on')

# -----------------------------------
# Create objects
# -----------------------------------
        varmintpest = varmintpest_fsm()
#        daylight_sensor = _daylight_sensor()

# -----------------------------------
# Create and start daemons
# -----------------------------------

# -----------------------------------
# Create and start graphics
# -----------------------------------

###############################################################
# Main program
###############################################################

        varmintpest.daylightYes()       # move to day time state
        varmintpest.daylightNo()        # move to night time state

        varmintpest.motionNotDetected() # move to day time state
        varmintpest.motionNotDetected() # move to day time state
        varmintpest.motionNotDetected() # move to day time state
        varmintpest.motionNotDetected() # move to day time state

        varmintpest.motionDetected() # move to day time state

        varmintpest.IRNotDetected() # move to day time state

        varmintpest.motionDetected() # move to day time state

        varmintpest.IRDetected() # move to day time state

        varmintpest.motionNotDetected() # move to day time state
        varmintpest.motionNotDetected() # move to day time state
        varmintpest.motionNotDetected() # move to day time state
        varmintpest.motionNotDetected() # move to day time state

        varmintpest.daylightYes() # move to day time state

        time.sleep(3.0)

###########################################################
# END
###########################################################
    except KeyboardInterrupt:
        logging.debug("Keyboard Interrupt exception!")
        exit()

    except BaseException as e:
        logging.error('General exception!: ' + str(e))

    # normal exit


text:
project_path: /web/_project.yaml
book_path: /web/fundamentals/_book.yaml

{# wf_updated_on: 2014-05-09 #}
{# wf_published_on: 2014-05-06 #}

# Optimalisasi Gambar {: .page-title }

{% include "web/_shared/contributors/ilyagrigorik.html" %}

Gambar sering menjadi sumber besarnya byte yang diunduh pada laman web dan juga sering kali menempati sejumlah besar ruang visual. Hasilnya, pengoptimalan gambar bisa menghasilkan beberapa penghematan byte terbesar dan meningkatkan kinerja situs web Anda: semakin sedikit byte yang harus diunduh browser, semakin sedikit persaingan untuk bandwidth klien dan lebih cepat browser mengunduh dan merender materi yang bermanfaat pada layar.

Optimalisasi gambar adalah seni dan juga sains: seni karena tidak ada satu jawaban pasti mengenai cara terbaik mengompresi satu gambar, dan sains karena ada banyak teknik yang cukup berkembang dan algoritme yang dapat mengurangi ukuran sebuah gambar secara signifikan. Menemukan setelan yang optimal untuk gambar Anda membutuhkan analisis yang cermat di banyak dimensi: kemampuan format, materi data yang dienkodekan, kualitas, dimensi piksel, dan lainnya.

## Meniadakan dan mengganti gambar

### TL;DR {: .hide-from-toc }
- Menghilangkan sumber daya gambar yang tidak perlu
- Memanfaatkan efek CSS3 jika memungkinkan
- Menggunakan font web daripada melakukan enkode teks dalam gambar

Pertanyaan yang pertama kali harus diajukan kepada diri sendiri apakah sebuah gambar, faktanya, diperlukan untuk mencapai efek yang Anda cari. Desain yang baik itu sederhana dan akan selalu menghasilkan kinerja terbaik. Jika Anda dapat meniadakan sumber daya gambar, yang sering kali membutuhkan sejumlah besar byte yang relatif terhadap HTML, CSS, JavaScript dan aset lainnya pada laman, maka hal itu selalu menjadi strategi optimalisasi terbaik. Dengan demikian, sebuah gambar yang ditempatkan dengan baik mengomunikasikan lebih banyak informasi daripada ribuan gambar, jadi terserah Anda untuk menemukan keseimbangan tersebut.

Berikutnya, Anda harus mempertimbangkan terlebih dahulu jika ada teknologi alternatif yang bisa mencapai hasil yang diinginkan, namun dengan cara yang lebih efisien:

* **Efek CSS** (gradasi, bayangan, dsb.) dan animasi CSS dapat digunakan untuk menghasilkan aset yang tidak tergantung pada resolusi yang selalu tampak tajam di setiap resolusi dan tingkat zoom, sering kali dalam byte yang lebih kecil daripada yang diperlukan oleh sebuah file gambar.
* **Font web** mengakibatkan penggunaan tipe desain yang indah sembari mempertahankan kemampuan untuk memilih, mencari, dan mengubah ukuran teks - sebuah peningkatan yang signifikan dalam kegunaan.

Jika Anda pernah mengenkodekan teks dalam sebuah aset gambar, berhentilah dan pertimbangkan kembali. Tipografi yang hebat sangat penting bagi desain yang baik, branding, dan keterbacaan, namun teks dalam gambar menghasilkan pengalaman pengguna yang buruk: teks tidak dapat dipilih, tidak dapat di-zoom, tidak dapat diakses, dan tidak cocok untuk perangkat ber-DPI tinggi. Penggunaan font web memerlukan [seperangkat optimalisasinya sendiri](https://www.igvita.com/2014/01/31/optimizing-web-font-rendering-performance/), namun penggunaan ini menjawab semua kekhawatiran ini dan selalu menjadi pilihan yang lebih baik untuk menampilkan teks.

## Gambar vektor vs. gambar bitmap

### TL;DR {: .hide-from-toc }
- Gambar vektor ideal untuk gambar yang terdiri dari bentuk-bentuk geometrik
- Gambar vektor tidak tergantung pada zoom dan resolusi
- Gambar bitmap harus digunakan untuk layar kompleks yang memiliki banyak bentuk dan detail yang tidak beraturan

Setelah Anda menentukan bahwa sebuah gambar memang faktanya merupakan format yang optimal untuk mencapai hasil yang diinginkan, pilihan penting berikutnya adalah untuk memilih format yang sesuai:

Gambar vektor yang diperbesar

Gambar bitmap yang diperbesar

* [Grafik vektor](https://en.wikipedia.org/wiki/Vector_graphics) menggunakan garis, titik, dan bentuk jamak untuk merepresentasikan sebuah gambar.
* [Grafis bitmap](https://en.wikipedia.org/wiki/Raster_graphics) merepresentasikan sebuah gambar dengan mengenkodekan setiap nilai dari setiap piksel di dalam grid berbentuk persegi panjang.

Setiap format memiliki kelebihan dan kekurangannya sendiri. Format vektor idealnya cocok untuk gambar yang terdiri dari bentuk geometris sederhana (misalnya logo, teks, ikon, dan seterusnya), dan memberikan hasil yang tajam di setiap setelan resolusi dan zoom, sehingga membuatnya menjadi format yang ideal untuk layar resolusi tinggi dan aset yang perlu ditampilkan pada beragam ukuran.

Akan tetapi, format vektor tidak cukup bila adegannya rumit (misalnya foto): jumlah markup SVG untuk menjelaskan semua bentuk menjadi terlalu tinggi dan keluarannya mungkin masih belum terlihat "fotorealistik". Bila itu yang terjadi, di situlah waktunya Anda harus menggunakan format gambar bitmap seperti GIF, PNG, JPEG atau salah satu format gambar yang lebih baru seperti JPEG-XR dan WebP.

Gambar bitmap tidak memiliki properti indah yang tidak tergantung pada resolusi atau zoom - ketika Anda menaikkan skala sebuah gambar bitmap, Anda akan melihat gambar yang kasar dan buram. Hasilnya, Anda mungkin harus menyimpan lebih dari satu versi gambar bitmap dengan resolusi yang bervariasi untuk menyajikan pengalaman yang optimal bagi pengguna.

## Implikasi layar resolusi tinggi

### TL;DR {: .hide-from-toc }
- Layar resolusi tinggi memiliki beberapa piksel perangkat per piksel CSS
- Gambar resolusi tinggi membutuhkan jumlah piksel dan byte yang jauh lebih tinggi
- Teknik optimalisasi gambarnya sama berapa pun resolusinya

Saat kita berbicara tentang piksel gambar, kita harus membedakan antara berbagai jenis piksel: Piksel CSS dan piksel perangkat. Satu piksel CSS tunggal dapat berisi banyak piksel perangkat - misalnya satu piksel CSS mungkin berkaitan secara langsung dengan satu piksel perangkat, atau mungkin didukung oleh banyak piksel perangkat. Apa maksudnya? Jadi semakin banyak pikselnya, semakin halus detail materi yang ditampilkan pada layar.

Layar DPI Tinggi (HiDPI) memproduksi hasil yang indah, namun ada satu kompromi yang jelas dibutuhkan: aset gambar kita membutuhkan lebih banyak detail untuk memanfaatkan hitungan piksel perangkat yang lebih tinggi. Kabar gembiranya, gambar vektor idealnya cocok untuk tugas ini, karena bisa di-render berapa pun resolusinya dengan hasil tajam - kita mungkin harus mengeluarkan biaya pemrosesan lebih tinggi untuk merender detail yang lebih halus, namun aset yang mendasari masih tetap sama dan bebas dari resolusi.

Di lain pihak, gambar bitmap memunculkan tantangan yang jauh lebih besar karena gambar ini mengenkode data gambar atas dasar per piksel. Oleh karena itu, semakin besar jumlah piksel, semakin besar ukuran file dari gambar bitmap. Misalnya, anggap perbedaan antara aset foto yang ditampilkan pada aset foto yang ditampilkan pada piksel 100x100 (CSS):

    Resolusi layar
    Total piksel
    Ukuran file tidak terkompresi (4 byte per piksel)

  1x
  100 x 100 = 10.000
  40.000 byte

  2x
  100 x 100 x 4 = 40.000
  160.000 byte

  3x
  100 x 100 x 9 = 90.000
  360.000 byte

Bila kita menggandakan resolusi layar fisik, jumlah total piksel akan bertambah empat kali lipat: dua kali jumlah piksel horizontal, lalu kalikan dengan dua kali jumlah piksel vertikal. Karena itu, "2x" layar tidak hanya berlipat dua kali, namun berlipat empat dari piksel yang diperlukan!

Jadi pada praktiknya seperti apa? Layar resolusi tinggi memungkinkan kita menghasilkan gambar yang indah, yang bisa jadi merupakan fitur produk yang hebat. Akan tetapi, layar resolusi tinggi juga membutuhkan gambar resolusi tinggi: pilihlah sedapat mungkin gambar vektor karena gambar ini bebas dari resolusi dan selalu menghasilkan gambar yang tajam, dan jika gambar bitmap diperlukan, berikan dan optimalkan beberapa variasi setiap gambar dengan bantuan [`srcset` dan `picture`](/web/fundamentals/design-and-ui/media/images#images-in-markup).

## Mengoptimalkan gambar vektor

### TL;DR {: .hide-from-toc }
- SVG adalah format gambar berbasis XML
- File SVG harus dikecilkan untuk mengurangi ukurannya
- File SVG harus dikompresikan dengan GZIP

Semua browser modern mendukung Scalable Vector Graphics (SVG), yang merupakan format gambar berbasis XML untuk grafis dua dimensi: kita bisa menyematkan markup SVG secara langsung pada laman, atau sebagai sumber daya eksternal. Pada gilirannya, sebuah file SVG dapat dibuat oleh sebagian besar perangkat lunak menggambar berbasis vektor, atau dengan tangan dan secara langsung dalam editor teks favorit Anda.

    <?xml version="1.0" encoding="utf-8"?>

    <svg version="1.2" baseProfile="tiny" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
       x="0px" y="0px" viewBox="0 0 612 792" xml:space="preserve">
    <g id="XMLID_1_">
      <g>
        <circle fill="red" stroke="black" stroke-width="2" stroke-miterlimit="10" cx="50" cy="50" r="40"/>
      </g>
    </g>
    </svg>

Contoh di atas merender satu bentuk lingkaran sederhana dengan garis luar hitam dan latar belakang merah dan diekspor dari Adobe Illustrator. Seperti yang bisa Anda lihat, gambar ini mengandung banyak metadata seperti informasi layer, komentar, dan namespace XML yang sering kali tidak diperlukan untuk merender aset dalam browser. Hasilnya, ada baiknya mengecilkan file SVG dengan menjalankan alat seperti [svgo](https://github.com/svg/svgo).

Yang terjadi di sini, svgo mengurangi ukuran file SVG di atas yang dihasilkan oleh Illustrator sebesar 58%, menguranginya dari 470 menjadi 199 byte. Selanjutnya, karena SVG merupakan format berbasis XML, kita juga dapat menerapkan kompresi GZIP untuk mengurangi ukuran transfernya - pastikan server Anda telah dikonfigurasikan untuk mengompresi aset SVG!

## Mengoptimalkan gambar bitmap

### TL;DR {: .hide-from-toc }
- Gambar bitmap adalah grid yang terdiri dari piksel
- Setiap piksel mengenkode informasi warna dan transparansi
- Kompresor gambar menggunakan berbagai variasi teknik untuk mengurangi jumlah bits per piksel yang diperlukan untuk mengurangi ukuran gambar

Gambar bitmap cuma grid 2 dimensi dari "piksel" individual - misalnya, gambar 100x100 piksel adalah rangkaian 10.000 piksel. Pada gilirannya, setiap piksel menyimpan nilai "[RGBA](https://en.wikipedia.org/wiki/RGBA_color_space)": saluran merah (R), saluran hijau (G), saluran biru (B), dan saluran (A) alfa (transparansi).

Secara internal, browser mengalokasikan 256 nilai (corak) untuk setiap saluran, yang diterjemahkan menjadi 8 bit per saluran (2 ^ 8 = 256), dan 4 byte per piksel (4 saluran x 8 bit = 32 bit = 4 byte). Hasilnya, jika kita tahu dimensi dari grid kita bisa menghitung ukuran file dengan mudah:

* Gambar 100 x 100px terdiri dari 10.000 piksel
* 10.000 piksel x 4 byte = 40.000 byte
* 40.000 byte / 1024 = 39 KB

Catatan: Sebagai cadangan, format gambar apa pun yang digunakan untuk mentransfer data dari server ke klien, bila gambar dienkode oleh browser, setiap piksel selalu menempati memori sebesar 4 byte. Ini bisa menjadi batasan penting untuk gambar besar dan perangkat yang tidak memiliki banyak ketersediaan memori - misalnya, perangkat seluler kelas bawah.

    Dimensi
    Piksel
    Ukuran file

  100 x 100
  10.000
  39 KB

  200 x 200
  40.000
  156 KB

  300 x 300
  90.000
  351 KB

  500 x 500
  250.000
  977 KB

  800 x 800
  640.000
  2.500 KB

39 KB untuk sebuah gambar berukuran 100x100 piksel mungkin tampak tidak terlalu besar, namun ukuran file dengan cepat bertambah besar untuk gambar yang lebih besar dan membuat aset gambar menjadi lambat dan mahal untuk diunduh. Untungnya yang telah kami jelaskan sejauh ini adalah format gambar yang "tidak dikompresi". Apa yang dapat kita lakukan untuk mengurangi ukuran file gambar?

Satu strategi sederhana adalah mengurangi "kedalaman bit" dari gambar dari 8 bit per saluran menjadi palet warna yang lebih kecil: 8 bit per saluran memberi kita 256 nilai per saluran dan total 16.777.216 (2563) warna. Bagaimana jika kita mengurangi palet menjadi 256 warna? Maka kita hanya akan membutuhkan 8 bit secara total untuk saluran RGB dan segera menyimpan dua byte per piksel -- yaitu 50% penghematan kompresi atas 4 byte asli per format piksel!

Catatan: Kiri ke kanan (PNG): 32-bit (16 juta warna), 7-bit (128 warna), 5-bit (32 warna). Gambar kompleks dengan transisi gradasi warna (gradasi, langit, dll.) membutuhkan palet warna lebih besar untuk menghindari artefak visual seperti langit yang pecah dalam aset 5 bit. Di sisi lain, jika gambar hanya menggunakan sedikit warna, maka palet besar hanya akan membuang-buang bit yang berharga!

Berikutnya, setelah mengoptimalkan data yang tersimpan dalam setiap piksel, kita bisa menjadi lebih cerdas dan juga melihat piksel terdekat: ternyata, banyak gambar, dan terutama foto, yang memiliki banyak piksel terdekat dengan warna serupa - misalnya langit, tekstur berulang, dan seterusnya. Dengan memanfaatkan informasi ini, kompresor dapat menerapkan "[enkode delta](https://en.wikipedia.org/wiki/Delta_encoding)" yang daripada menyimpan masing-masing nilai untuk setiap piksel, kita bisa menyimpan selisih antara piksel di sekitarnya: jika piksel yang terdekat sama, maka deltanya adalah "nol" dan kita hanya perlu menyimpan satu bit! Namun kita tidak perlu berhenti di situ...

Mata manusia memiliki tingkat sensitivitas yang berbeda terhadap warna yang berbeda: kita dapat mengoptimalkan enkode warna kita untuk diperhitungkan dengan mengurangi atau menambah palet bagi warna tersebut.
Piksel "Di Sekitar" membentuk dua grid dimensi, yang artinya bahwa setiap piksel memiliki beberapa tetangga: kita bisa menggunakan fakta ini untuk lebih menyempurnakan enkode delta.
Daripada hanya melihat tetangga terdekat di setiap piksel, kita dapat melihat blok piksel terdekat yang lebih besar dan mengenkode blok berbeda dengan setelan berbeda. Dan seterusnya...

Seperti yang bisa Anda lihat, optimalisasi gambar menjadi semakin rumit (atau menyenangkan, tergantung cara Anda melihatnya), dan terus menjadi bidang penelitian di kalangan akademisi dan komersial. Gambar menempati banyak byte dan mengembangkan teknik kompresi gambar yang lebih baik akan sangat bermanfaat! Jika Anda penasaran untuk mempelajari lebih lanjut, buka [laman Wikipedia](https://en.wikipedia.org/wiki/Image_compression), atau lihat [dokumen resmi teknik kompresi WebP](/speed/webp/docs/compression) untuk melihat contohnya secara langsung.

Jadi sekali lagi, ini semuanya sangat menyenangkan, namun juga sangat akademik: bagaimana hal itu bisa membantu kita mengoptimalkan gambar dalam laman kita? Kita tidak berada dalam posisi untuk menemukan teknik kompresi yang baru, namun kita harus memahami bentuk masalahnya: Piksel RGBA, kedalaman bit, dan berbagai teknik optimalisasi. Semua konsep ini penting untuk dipahami dan diingat sebelum kita terjun ke dalam diskusi mengenai berbagai variasi format gambar bitmap.

## Kompresi gambar lossless vs lossy

### TL;DR {: .hide-from-toc }
- Karena cara kerja mata kita, gambar paling cocok untuk melakukan kompresi lossy
- Optimalisasi gambar merupakan fungsi dari kompresi melalui kompresi lossy dan lossless
- Perbedaan dalam format gambar karena perbedaan dalam cara dan algoritme lossy dan lossless yang digunakan untuk mengoptimalkan gambar.
- Tidak ada format atau "setelan kualitas" terbaik untuk semua gambar: setiap kombinasi kompresor dan materi gambar tertentu akan menghasilkan keluaran unik.

Untuk data jenis tertentu, seperti kode sumber untuk sebuah laman, atau file yang dapat dieksekusi, kompresor harus tidak boleh mengubah atau menghilangkan setiap informasi asli: satu bit data yang hilang atau salah saja dapat mengubah seluruh arti materi file, atau lebih buruk lagi, merusaknya secara keseluruhan. Untuk beberapa jenis data, seperti gambar, audio, dan video, menghasilkan "perkiraan" representasi data asli mungkin dapat diterima secara wajar.

Sebenarnya, karena cara kerja mata, kita sering kali bisa menghindari pembuangan banyak informasi tentang setiap piksel untuk mengurangi ukuran file gambar - misalnya, mata kita memiliki sensitivitas berbeda terhadap warna berbeda, artinya kita bisa menggunakan lebih sedikit bit untuk mengenkode sebagian warna. Hasilnya, sebuah pipeline proses optimalisasi gambar tipikal akan terdiri dari dua langkah tingkat tinggi:

1. Gambar diproses dengan filter "[lossy](https://en.wikipedia.org/wiki/Lossy_compression)" yang meniadakan beberapa data piksel
1. Gambar diproses dengan filter "[lossless](https://en.wikipedia.org/wiki/Lossless_compression)" yang mengompresi data piksel

**Langkah pertama adalah opsional, dan algoritme sebenarnya akan bergantung pada format gambar tertentu, namun masih perlu dipahami bahwa setiap gambar bisa melalui langkah kompresi lossy untuk mengurangi ukurannya.** Faktanya, perbedaan antara berbagai format gambar, seperti GIF, PNG, JPEG, dan lainnya adalah kombinasi algoritme spesifik yang digunakan (atau dihilangkan) ketika menerapkan langkah lossy dan lossless.

Jadi apa konfigurasi optimalisasi lossy dan lossless yang "optimal"? Jawabannya bergantung pada materi gambar dan kriteria Anda sendiri seperti kompensasi antara ukuran gambar dan artefak yang diperkenalkan oleh kompresi lossy: dalam beberapa hal Anda mungkin ingin melewatkan optimalisasi lossy untuk mengomunikasikan detail yang rumit dengan sepenuhnya mengikuti keasliannya, dan dalam hal lainnya Anda dapat menerapkan optimalisasi lossy agresif untuk mengurangi ukuran file dari aset gambar.  Di sinilah pertimbangan Anda dan konteks perlu digunakan - tidak ada setelan yang berlaku umum.

Sebagai contoh langsung, saat menggunakan format lossy seperti JPEG, kompresor biasanya akan mengekspos setelan "kualitas" yang dapat disesuaikan (misalnya, slider kualitas yang disediakan oleh fungsionalitas "Save for Web" di Adobe Photoshop), yang biasanya berupa angka antara 1 dan 100 yang mengontrol cara kerja bagian dalam dari kumpulan algoritme lossy dan lossless tertentu. Untuk hasil yang terbaik, lakukan eksperimen pada beberapa setelan kualitas untuk gambar Anda, dan jangan takut untuk menurunkan kualitas - hasil visualnya sering kali sangat bagus dan penghematan ukuran filenya bisa cukup besar.

Catatan: Perhatikan, tingkat kualitas untuk berbagai format gambar tidak dapat dibandingkan secara langsung karena perbedaan algoritme yang digunakan untuk mengenkode gambar: kualitas 90 JPEG akan memberikan hasil yang jauh berbeda dari kualitas 90 WebP. Sebenarnya, bahkan tingkat kualitas untuk format gambar sama mungkin menghasilkan keluaran berbeda yang kentara berdasarkan implementasi kompresor!

## Memilih format gambar yang tepat

### TL;DR {: .hide-from-toc }
- Mulai dengan memilih format universal yang tepat: GIF, PNG, JPEG
- Lakukan eksperimen dan pilih setelan terbaik untuk setiap format: kualitas, ukuran palet, dll.
- Pertimbangkan untuk menambahkan WebP dan aset JPEG XR untuk klien modern.

Selain algoritme kompresi lossy dan lossless yang berbeda, format gambar berbeda mendukung fitur seperti animasi dan saluran transparansi (alfa). Hasilnya, pilihan "format yang tepat" untuk gambar tertentu merupakan paduan dari hasil visual yang diinginkan dengan persyaratan fungsional.

    Format
    Transparansi
    Animasi
    Browser

  GIF
  Ya
  Ya
  Semua

  PNG
  Ya
  Tidak
  Semua

  JPEG
  Tidak
  Tidak
  Semua

  JPEG XR
  Ya
  Ya
  IE

  WebP
  Ya
  Ya
  Chrome, Opera, Android

Ada tiga format gambar yang didukung secara universal: GIF, PNG, dan JPEG. Selain dari format ini, sebagian browser juga mendukung format yang lebih baru seperti WebP dan JPEG XR, yang menawarkan kompresi keseluruhan lebih baik dan fitur lebih banyak. Jadi, format manakah yang harus Anda gunakan?

1. **Apakah Anda butuh animasi? Jika demikian, GIF adalah satu-satunya pilihan universal.**
    * GIF membatasi palet warna maksimal hingga 256 warna, sehingga merupakan pilihan yang buruk bagi sebagian besar gambar. Selanjutnya, PNG-8 menghasilkan kompresi lebih baik untuk gambar dengan palet kecil. Hasilnya, GIF merupakan jawaban yang tepat hanya ketika animasi diperlukan.
1. **Apakah Anda perlu mempertahankan detail yang halus dengan resolusi tertinggi? Gunakan PNG.**
    * PNG tidak berlaku untuk setiap algoritme kompresi lossy di luar pilihan ukuran palet warna. Hasilnya, gambar yang berkualitas tertinggi, namun dampaknya adalah ukuran file yang lebih tinggi daripada format lainnya. Gunakanlah dengan bijak.
    * Jika aset gambar berisi citra yang terdiri dari bentuk-bentuk geometrik, pertimbangkan untuk mengonversikannya menjadi format vektor (SVG)!
    * Jika aset gambar mengandung teks, hentikan, dan pertimbangkan kembali. Teks dalam gambar tidak dapat dipilih, dicari, atau "dapat di-zoom". Jika Anda harus menghasilkan tampilan khusus (untuk branding atau alasan lain), gunakan font web saja.
1. **Apakah Anda mengoptimalkan foto, tangkapan layar, atau aset gambar serupa? Gunakan JPEG.**
    * JPEG menggunakan paduan optimalisasi lossy dan lossless untuk mengurangi ukuran file dari aset gambar. Cobalah beberapa tingkat kualitas JPEG untuk menemukan kompensasi kualitas terbaik vs. ukuran file untuk aset Anda.

Terakhir, setelah Anda menentukan format gambar yang optimal dan setelannya untuk setiap aset Anda, pertimbangkan untuk menambahkan varian tambahan yang dienkodekan dalam WebP dan JPEG XR. Kedua format ini baru, dan sayangnya tidak (belum) didukung secara universal oleh semua browser, namun keduanya bisa memberikan penghematan signifikan untuk klien yang lebih baru - misalnya, rata-rata WebP menghasilkan [penurunan ukuran file sebesar 30%](/speed/webp/docs/webp_study) dibandingkan gambar JPEG yang setara.

Karena baik WebP maupun JPEG XR tidak didukung secara universal, Anda harus menambahkan logika tambahan ke aplikasi atau server Anda untuk melayani sumber daya yang sesuai:

* Sebagian CDN menyediakan optimalisasi gambar sebagai layanan, termasuk penyerahan JPEG XR dan WebP.
* Sebagian alat sumber terbuka (misalnya, PageSpeed untuk Apache atau Nginx) mengotomatiskan optimalisasi, konversi, dan penyajian aset yang sesuai.
* Anda bisa menambah logika aplikasi tambahan untuk mendeteksi klien, memeriksa format mana yang didukung, dan menyajikan format gambar terbaik dari yang ada.

Terakhir, perhatikan bahwa jika Anda menggunakan Webview untuk merender materi dalam aplikasi asli, maka Anda memiliki kendali penuh atas klien dan dapat menggunakan WebP secara eksklusif! Facebook, Google, dan banyak lagi yang lainnya memakai WebP untuk menayangkan semua gambar mereka di dalam aplikasi - penghematannya benar-benar bermanfaat. Untuk mengetahui selengkapnya tentang WebP, lihat presentasi [WebP: Deploying Faster, Smaller, and More Beautiful Images](https://www.youtube.com/watch?v=pS8udLMOOaE) dari Google I/O 2013.

## Alat dan penyempurnaan parameter

Tidak ada satu pun format gambar yang sempurna, alat, atau seperangkat parameter optimalisasi yang bisa diterapkan ke semua gambar. Untuk mendapatkan hasil terbaik, Anda harus memilih format dan setelan bergantung pada materi dari gambar, dan visualnya serta persyaratan teknis lainnya.

    Alat
    Keterangan

  gifsicle
  membuat dan mengoptimalkan gambar GIF

  jpegtran
  mengoptimalkan gambar JPEG

  optipng
  optimalisasi PNG lossless

  pngquant
  optimalisasi PNG lossy

Jangan takut untuk bereksperimen dengan parameter dari setiap kompresor. Turunkan kualitas, lihat tampilannya, lalu, rapikan, dan ulangi. Setelah menemukan satu set setelan yang baik, Anda dapat menerapkannya ke gambar serupa pada situs, namun jangan anggap semua gambar harus dikompresikan dengan setelan yang sama.

## Menghasilkan aset gambar yang diubah skalanya

### TL;DR {: .hide-from-toc }
- Mencapai aset berskala adalah salah satu optimalisasi paling sederhana dan efektif
- Perhatikan dengan cermat aset besar karena hasilnya adalah overhead yang tinggi
- Kurangi jumlah piksel yang tidak perlu dengan menyesuaikan skala gambar dengan ukuran gambarnya

Optimalisasi gambar pada akhirnya terdari dua kriteria: mengoptimalkan jumlah byte yang digunakan untuk mengenkode setiap piksel gambar, dan mengoptimalkan jumlah total piksel: ukuran file gambar hanyalah jumlah total piksel dikalikan jumlah byte yang digunakan untuk mengenkode setiap piksel. Tidak kurang, tidak lebih.

Hasilnya, salah satu teknik optimalisasi gambar paling sederhana dan efektif adalah memastikan bahwa kita tidak memberikan piksel tambahan lagi selain dari yang diperlukan untuk menampilkan aset pada ukuran yang diinginkan dalam browser. Terdengar sederhana bukan? Sayangnya, kebanyakan laman gagal dalam ujian ini untuk mayoritas aset gambarnya: biasanya, laman menerjunkan aset yang lebih besar dan mengandalkan browser untuk mengubah skalanya - yang juga akan menghabiskan lebih banyak sumber daya CPU - dan menampilkannya pada resolusi lebih rendah.

Catatan: Mengarahkan kursor ke atas elemen gambar dalam Chrome DevTools akan memunculkan ukuran "alami" dan "tampilan" dari aset gambar. Dalam contoh di atas, gambar 300x260 piksel diunduh namun kemudian skalanya diturunkan (245x212) di klien saat ditampilkan.

Overhead penyampaian piksel yang tak perlu - hanya untuk browser mengubah skala gambar untuk Anda, berarti hilangnya kesempatan besar untuk mengurangi dan mengoptimalkan jumlah byte yang diperlukan untuk merender laman. Selanjutnya, perhatikan bahwa mengubah ukuran tidak hanya memfungsikan pengurangan jumlah piksel gambar, namun juga tentang ukuran alaminya.

    Resolusi layar
    Ukuran alami
    Ukuran tampilan (CSS px)
    Piksel yang tidak perlu

  1x
  110 x 110
  100 x 100
  110 x 110 - 100 x 100 = 2.100

  1x
  410 x 410
  400 x 400
  410 x 410 - 400 x 400 = 8.100

  1x
  810 x 810
  800 x 800
  810 x 810 - 800 x 800 = 16.100

  2x
  220 x 220
  100 x 100
  210 x 210 - (2 x 100) x (2 x 100) = 8.400

  2x
  820 x 820
  400 x 400
  820 x 820 - (2 x 400) x (2 x 400) = 32.400

  2x
  1.620 x 1.620
  800 x 800
  1.620 x 1.620 - (2 x 800) x (2 x 800) = 64.400

Perhatikan bahwa dalam semua contoh di atas, ukuran tampilan "hanya 10 CSS piksel lebih kecil" daripada aset yang diperlukan untuk setiap resolusi layar. Akan tetapi jumlah piksel tambahan, dan overhead-nya yang terkait, semakin cepat meningkat seiring meningkatnya dimensi tampilan gambar! Hasilnya, meski Anda mungkin tidak dapat menjamin bahwa setiap aset tunggal disajikan pada ukuran tampilannya secara persis, **Anda harus memastikan bahwa jumlah piksel yang tidak diperlukan jumlahnya minimal, dan bahwa aset besar Anda secara khusus disampaikan sedekat mungkin ke ukuran tampilannya.**

## Daftar periksa optimalisasi gambar

Optimalisasi gambar adalah seni dan juga sains: seni karena tidak ada satu jawaban pasti mengenai cara terbaik mengompresi satu gambar, dan sains karena ada banyak teknik yang cukup berkembang dan algoritme yang dapat membantu mengurangi ukuran sebuah gambar secara signifikan.

Beberapa tip dan teknik untuk diingat saat Anda mengoptimalkan gambar:

* **Pilih format vektor:** gambar vektor tidak tergantung pada resolusi dan skala, sehingga gambar vektor sangat cocok untuk lingkungan dengan berbagai jenis perangkat dan resolusi tinggi.
* **Kecilkan dan kompresikan aset SVG:** Markup XML yang dihasilkan oleh kebanyakan aplikasi gambar sering kali mengandung metadata yang tidak perlu yang bisa dibuang; pastikan bahwa server telah dikonfigurasikan untuk menerapkan kompresi GZIP untuk aset SVG.
* **Pilih format gambar bitmap terbaik:** tentukan persyaratan fungsional Anda dan pilih yang paling cocok untuk aset tertentu.
* **Lakukan eksperimen dengan setelan kualitas yang optimal untuk format bitmap:** jangan takut untuk menurunkan setelan "kualitas", hasilnya sering kali sangat bagus dan penghematan byte-nya signifikan.
* **Buang metadata gambar yang tidak perlu:** banyak gambar bitmap mengandung metadata yang tidak perlu mengenai aset: informasi geografi, informasi kamera, dst. Gunakan alat yang sesuai untuk mengurai data ini.
* **Sajikan gambar berskala:** ubah ukuran gambar pada server dan pastikan bahwa ukuran "tampilan" sedekat mungkin dengan ukuran gambar "alami". Perhatikan dengan cermat khususnya gambar berukuran besar, karena merupakan faktor overhead terbesar saat diubah ukurannya.
* **Otomatiskan, otomatiskan, otomatiskan:** investasikan pada alat otomatis dan prasarana yang akan memastikan bahwa semua aset gambar Anda dioptimalkan.

{# wf_devsite_translation #}