diff --git a/Data/Conduit/ImageSize.hs b/Data/Conduit/ImageSize.hs
new file mode 100644
--- /dev/null
+++ b/Data/Conduit/ImageSize.hs
@@ -0,0 +1,89 @@
+{-# LANGUAGE OverloadedStrings #-}
+module Data.Conduit.ImageSize
+    ( sinkImageSize
+    , Size (..)
+    ) where
+
+import qualified Data.ByteString.Lazy as L
+import qualified Data.Conduit as C
+import qualified Data.Conduit.Binary as CB
+import qualified Data.ByteString as S
+import Data.ByteString.Char8 ()
+import Data.ByteString.Lazy.Char8 ()
+import Control.Applicative ((<$>), (<*>))
+
+data Size = Size { width :: Int, height :: Int }
+    deriving (Show, Eq, Ord, Read)
+
+sinkImageSize :: C.Resource m => C.Sink S.ByteString m (Maybe Size)
+sinkImageSize =
+    C.SinkData (pushHeader id) close
+  where
+    close = return Nothing
+    pushHeader front bs'
+        | S.length bs >= 11 && S.take 5 (S.drop 6 bs) ==
+            S.pack [0x4A, 0x46, 0x49, 0x46, 0x00] =
+                C.sinkPush jpg $ S.drop 4 bs
+        | S.length bs >= 6 && S.take 6 bs `elem` gifs =
+            C.sinkPush gif $ S.drop 6 bs
+        | S.length bs >= 8 && S.take 8 bs == S.pack [137, 80, 78, 71, 13, 10, 26, 10] =
+            C.sinkPush png $ S.drop 8 bs
+        | S.length bs < 11 = do
+            return $ C.Processing (pushHeader $ S.append bs) close
+        | otherwise = do
+            return $ C.Done (Just bs) Nothing
+      where
+        bs = front bs'
+
+    gifs = ["GIF87a", "GIF89a"]
+    gif = do
+        b <- CB.take 4
+        let go x y = fromIntegral x + (fromIntegral y) * 256
+        return $ case L.unpack b of
+            [w1, w2, h1, h2] -> Just $ Size (go w1 w2) (go h1 h2)
+            _ -> Nothing
+
+    png = do
+        _ <- CB.take 4 -- FIXME drop
+        hdr <- CB.take 4
+        if hdr == "IHDR"
+            then do
+                mw <- getInt 4 0
+                mh <- getInt 4 0
+                return $ Size <$> mw <*> mh
+            else return Nothing
+
+
+    jpg = do
+        mi <- getInt 2 0
+        case mi of
+            Nothing -> return Nothing
+            Just i -> do
+                _ <- CB.take $ i - 2
+                jpgFrame
+
+    jpgFrame = do
+        mx <- CB.head
+        case mx of
+            Just 255 -> do
+                my <- CB.head
+                case my of
+                    Just 0xC0 -> do
+                        _ <- CB.take 3
+                        h <- getInt 2 0
+                        w <- getInt 2 0
+                        return $ Size <$> w <*> h
+                    Just _ -> jpg
+                    Nothing -> return Nothing
+            _ -> return Nothing
+
+getInt :: (C.Resource m, Integral i)
+       => Int
+       -> i
+       -> C.Sink S.ByteString m (Maybe i)
+getInt 0 i = return $ Just i
+getInt len i = do
+    mx <- CB.head
+    case mx of
+        Nothing -> return Nothing
+        Just x -> getInt (len - 1) (i * 256 + fromIntegral x)
diff --git a/LICENSE b/LICENSE
new file mode 100644
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,30 @@
+Copyright (c)2011, Michael Snoyman
+
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+    * Redistributions of source code must retain the above copyright
+      notice, this list of conditions and the following disclaimer.
+
+    * Redistributions in binary form must reproduce the above
+      copyright notice, this list of conditions and the following
+      disclaimer in the documentation and/or other materials provided
+      with the distribution.
+
+    * Neither the name of Michael Snoyman nor the names of other
+      contributors may be used to endorse or promote products derived
+      from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/Setup.lhs b/Setup.lhs
new file mode 100644
--- /dev/null
+++ b/Setup.lhs
@@ -0,0 +1,7 @@
+#!/usr/bin/env runhaskell
+
+> module Main where
+> import Distribution.Simple
+
+> main :: IO ()
+> main = defaultMain
diff --git a/imagesize-conduit.cabal b/imagesize-conduit.cabal
new file mode 100644
--- /dev/null
+++ b/imagesize-conduit.cabal
@@ -0,0 +1,39 @@
+Name:                imagesize-conduit
+Version:             0.2.0
+Synopsis:            Determine the size of some common image formats.
+Description:         Currently supports PNG, GIF, and JPEG. This package provides a Sink that will consume the minimum number of bytes necessary to determine the image dimensions.
+License:             BSD3
+License-file:        LICENSE
+Author:              Michael Snoyman
+Maintainer:          michael@snoyman.com
+Category:            Graphics, Conduit
+Build-type:          Simple
+Cabal-version:       >=1.8
+Homepage:            http://github.com/snoyberg/conduit
+extra-source-files:  test/main.hs
+                     test/logo.png
+                     test/logo.gif
+                     test/logo.jpg
+
+Library
+  Exposed-modules:     Data.Conduit.ImageSize
+  Build-depends:       base                     >= 4            && < 5
+                     , conduit                  >= 0.2          && < 0.3
+                     , bytestring               >= 0.9
+  ghc-options:     -Wall
+
+test-suite test
+    hs-source-dirs: test
+    main-is: main.hs
+    type: exitcode-stdio-1.0
+    build-depends:   conduit
+                   , imagesize-conduit
+                   , base
+                   , hspec
+                   , HUnit
+                   , bytestring
+    ghc-options:     -Wall
+
+source-repository head
+  type:     git
+  location: git://github.com/snoyberg/conduit.git
diff --git a/test/logo.gif b/test/logo.gif
new file mode 100644
Binary files /dev/null and b/test/logo.gif differ
diff --git a/test/logo.jpg b/test/logo.jpg
new file mode 100644
Binary files /dev/null and b/test/logo.jpg differ
diff --git a/test/logo.png b/test/logo.png
new file mode 100644
Binary files /dev/null and b/test/logo.png differ
diff --git a/test/main.hs b/test/main.hs
new file mode 100644
--- /dev/null
+++ b/test/main.hs
@@ -0,0 +1,21 @@
+{-# LANGUAGE OverloadedStrings #-}
+import qualified Data.Conduit as C
+import qualified Data.Conduit.Binary as CB
+import Data.Conduit.ImageSize
+
+import Test.Hspec.Monadic
+import Test.Hspec.HUnit ()
+import Test.HUnit
+
+main :: IO ()
+main = hspecX $ do
+    describe "image size" $ do
+        it "png" $ check (Just $ Size 271 61) "test/logo.png"
+        it "jpg" $ check (Just $ Size 271 61) "test/logo.jpg"
+        it "gif" $ check (Just $ Size 271 61) "test/logo.gif"
+        it "invalid" $ check Nothing "test/main.hs"
+
+check :: Maybe Size -> FilePath -> Assertion
+check ex fp = do
+    size <- C.runResourceT $ CB.sourceFile fp C.$$ sinkImageSize
+    size @?= ex
