       readNext()
 
     df.promise()
+
+  ns.replaceJpegChunk = (blob, marker, chunks) ->
+    df = $.Deferred()
+    oldChunkPos = []
+    oldChunkLength = []
+
+    op = ns.readJpegChunks(blob)
+    op.fail(df.reject)
+    op.progress (pos, length, oldMarker) ->
+      if oldMarker == marker
+        oldChunkPos.push(pos)
+        oldChunkLength.push(length)
+    op.done ->
+      newChunks = [blob.slice(0, 2)]
+      for chunk in chunks
+        intro = new DataView(new ArrayBuffer(4))
+        intro.setUint16(0, 0xff00 + marker)
+        intro.setUint16(2, chunk.byteLength + 2)
+        newChunks.push(intro.buffer)
+        newChunks.push(chunk)
+
+      pos = 2
+      for i in [0...oldChunkPos.length]
+        if oldChunkPos[i] > pos
+          newChunks.push(blob.slice(pos, oldChunkPos[i]))
+        pos = oldChunkPos[i] + oldChunkLength[i] + 4
+      newChunks.push(blob.slice(pos, blob.size))
+
+      df.resolve(new Blob(newChunks, {type: blob.type}))
+
+    df.promise()

        op = ns.reduceImage(img, settings)
        op.progress (progress) ->
          console.log(progress)
        op.fail df.reject
        op.done (canvas) ->
          # start = new Date()
          utils.canvasToBlob canvas, 'image/jpeg', settings.quality or 0.95,
            (blob) ->
              if exif
                intro = new DataView(new ArrayBuffer(6))
                intro.setUint32(0, 0xffd8ffe1)
                intro.setUint16(4, exif.byteLength + 2)
                blob = new Blob([
                  intro, exif, blob.slice(2, blob.size)
                ], {type: blob.type})
              # console.log('to blob: ' + (new Date() - start))
              df.resolve(blob)

      img.onerror = ->
        df.reject('not image')

        readToView file.slice(pos, pos += length), (view) ->
          if view.byteLength != length
            return df.reject('corrupted')
          df.notify(startPos, length, marker, view)
          readNext()

    df = $.Deferred()
