らくがき

2006/11/08

Creating an IMG SRC handler

具体的なアドオンの作成方法が載っていた。(現在キャッシュのみ) Read more!

Creating an IMG SRC handler

I've investigated the mechanism of Internet Explorer's <IMG SRC> handler for these days. And I've found it is very easy to create my own IMG SRC handler! In this document, I explain the procedure to create your own one.

Step 1

You must create a empty ATL object, which adopts the single thread apartment. And add the interface entry of IImageDecodeFilter.
If you're working with Visual Studio .Net Environment, your ATL wizard creates quite a simple files without good old IDL files. It would be like as:

class ATL_NO_VTABLE CMyImageDecodeFilter :
  // <Prior version of VC++ inserts many ancestor classes here>
  public IMyImageDecodeFilter,
  public IImageDecodeFilter
{
public:
  CMyImageDecodeFilter()
  {
  }

  DECLARE_PROTECT_FINAL_CONSTRUCT()

  HRESULT FinalConstruct()
  {
    return S_OK;
  }

  void FinalRelease()
  {
  }

BEGIN_COM_MAP(CMyImageDecodeFilter)
  COM_INTERFACE_ENTRY_IID(IID_IImageDecodeFilter, IImageDecodeFilter)
END_COM_MAP()

public:
  // The methods we've inherited from IImageDecodeFilter class
  STDMETHOD(Initialize)(IImageDecodeEventSink *pIDES);
  STDMETHOD(Process)(IStream *stream);
  STDMETHOD(Terminate)(HRESULT);
};

I've removed many extra staffs that may confuse you to understand what's going on.

Step 2

Now All we have to do with this class is to implement 3 methods. Initialize, the first method, is called at first. The caller provides us the most important parameter pIDES.
Although IImageDecodeEventSink class is too large for me to analyze whole of it, it is very clear that pIDES knows everything about the canvas (they call it surface) on which we are going to draw. We must use this instance pointer in the other methods, so keep this pointer to some member function and also increasing reference of it by calling AddRef method. And this is just the time to call the important method, IImageDecodeEventSink::OnBeginDecode.
  HRESULT IImageDecodeEventSink::OnBeginDecode( DWORD* pdwEvents, ULONG* pnFormats, BFID** ppFormats);
The first parameter seems to return the events to be invoked by us. It seems that it returns the list of acceptable pixel formats as the second parameter. And the third parameter indicates the number of entries. In the sample below, we checks the existence of RGB 24-bit format, BFID_RGB_24 and if there is not, immediately return E_FAIL, otherwise return S_OK.
STDMETHODIMP CMyImageDecodeFilter::Initialize(IImageDecodeEventSink *pIDES)
{
  m_pIDES = pIDES;
  m_pIDES->AddRef();

  if(FAILED(m_pIDES->OnBeginDecode(&m_dwEvents, &nFormats, &pFormats)))
  {
    m_pIDES->Release();
    return E_FAIL;
  }

  // find the prefer BFID (Colorspace) to use
  ULONG i;
  for(i = 0; i < nFormats; i++)
    if(IsEqualGUID(BFID_RGB_24, pFormats[i]))
      break;

  if(i == nFormats)
    return E_FAIL;
  return S_OK;
}

Step 3

The next step is to implement CMyImageDecodeFilter::Process.
  HRESULT CMyImageDecodeFilter::Process(IStream *stream);
This function is called when Internet Explorer want to process an image of the our image type, and we can read the image file by using the parameter stream.
There's an important notice, although this parameter is IStream based class, we can not make random access codes rely on it. It only accepts sequential Read method calls. No other methods are implemented; you can not get the size of the file.
To make matters worse, the Block read using Read methods does not notify me the correct EOF. For example, reading 1024-bytes each time, the last block, which is no more than 1024-bytes, can not be obtained. A file that is 2060-bytes is corrupted into 2048-bytes file. So we must read the file carefully.

Step 4

When we've loaded the file, draw the image onto the surface that can be obtained by the IImageDecodeEventSink::GetSurface method. If you are an experienced DirectDraw programmer, calling this method is quite easy. Unfortunately I've never experience programming DirectDraw, I may write something wrong in this section. If you notice something bad in this article, please inform me of them.
Anyway, the interface of this function is defined as follows:
  HRESULT IImageDecodeEventSink::GetSurface(
    LONG width, LONG height, REFGUID bfid, ULONG nPasses, DWORD dwHints, IUnknown **ppUnk);
To get the surface, we pass the parameters, width, height and pixel format of the image. For some reason, Internet Explorer can not handle large images and we have to do something if the image is quite large. And it also seems that Internet Explorer now supports only 24-bit RGB surfaces (BFID_RGB_24). GetSurface method gets 2 more parameters except the pointer to the buffer to get interface. nPasses seems to be the passes to decode the image, but I'm not sure of the real usage of this parameter, indeed, the multi-pass decoding can be done even if the parameter set to 1. dwHints is the flags to specify some additional attributes. The details about this parameter are also unknown. We set dwHints to IMGDECODE_HINT_BOTTOMUP | IMGDECODE_HINT_FULLWIDTH. The one thing that is apparent to me is that IMGDECODE_HINT_BOTTOMUP indicates the usual bottom-up DIB. But I don't know about IMGDECODE_HINT_FULLWIDTH. What's this?

Step 5

OK, anyway, we've get the IDirectDrawSurface interface pointer. That's the time to paint on it! But there're also some restrictions on this interface. Almost all methods are not implemented. The only one method we can use with this interface is Lock/Unlock. We must build images on DIB by using raw pointers! I'm sorry but there seems to be only one way to do it. IDirectDrawSurface::Lock method is defined as follows:

  HRESULT Lock(
    LPRECT rc, LPDDSURFACEDESC pddsd, DWORD dwFlags, HANDLE hEvent);

The first parameter rc is region to paint on. Although it can be the whole of the surface, as far as DirectDraw reference, locking the large part of the image long time causes poor system performance. Anyway we set the whole of image to paint on it. The next parameter is to get the details of the surface. All we have to do with it is to set DDSUTFACEDESC::dwSize to sizeof(DDSUTFACEDESC). And set dwFlags to DDLOCK_WAIT, don't ask me about it. The last one, hEvent is set to NULL.
If the method succeeded, we get the pointer to the raw DIB buffer on DDSURFACEDESC::lpSurface member. The row-stride, the bytes per line, is obtained in DDSURFACEDESC::lPitch. For the image manipulation basics, refer to other resources, I don't want to explain what is row-stride or pitch.
After drawing images on the buffer, we must unlock the buffer by calling Unlock method with the region passed to Lock method.
Now we must inform the Internet Explorer of updating the image by using IImageDecodeEventSink::OnProgress method that is defined as follows:

  HRESULT OnProgress(LPRECT rc, BOOL bComplete);

We call this function only if the m_dwHints, the first parameter of IImageDecodeEventSink::OnBeginDecode contains IMGDECODE_EVENT_PROGRESS. The first parameter rc is updated region, same one to previous calls. The second one, bComplete indicates whether the drawing of the image is finished or not. If this is not FALSE, there will be some subsequent calls after that.
Call IImageDecodeEventSink::OnDecodeComplete method when finishing all of the drawing process. This method has a HRESULT type parameter to indicates errors. If the drawing process is success, simply pass S_OK.
As already you've noticed, the image format we are now reading supports progressive feature or something like it, we can show the image progressively to the user.

Step 6

To register this handler, we create an entry like below:

HKCR\.some_extension
  (Default) REG_SZ "someImageType"
  "Content Type" REG_SZ "some_image_type"

HKCR\SomeImageType\CLSID
  (Default) REG_SZ "{25336920-03F9-11cf-8FD0-00AA00686F13}" (1)

HKCR\MIME\Database\Content Type\some_image_type
  "Extension" REG_SZ ".some_extension"
  "Image Filter CLSID" REG_SZ "{XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX}" (2)

HKEY\MIME\Database\Content Type\some_image_type\Bits
  "0" REG_BINARY See Below (3)

The entry (1) is needed to view the direct link (<A HREF>) to the image in the browser. (2) is string notation of the CLSID of your component.
(3) is a binary string that identify the image file. For Windows bitmap files, this is
02 00 00 00 ff ff 42 4d
The first 4-bytes are the size of identifier. The next is 0xff padding, which size is specified by the first 4-bytes. The last 2-bytes are actual identifier of the bitmap file, "BM". In the same way, GIF file has the following one:
04 00 00 00 ff ff ff ff 47 49 46 38
The last 4-bytes are "GIF8". WMF (Windows Meta File) file have the following:
04 00 00 00 ff ff ff ff d7 cd c6 9a
PNG has a longer one.
08 00 00 00 ff ff ff ff ff ff ff ff 89 50 4e 47 0d 0a 1a 0a
But the most confusing one is JPEG. JPEG has 2 entries "image/jpeg" and "image/pjpeg". Each entry has same one.
02 00 00 00 ff ff ff 8d
Pay attention to the third 0xff. This is a part of file identifier, not the padding. If you know something related this article, please inform me by e-mails. I appreciate your opinions. mailto:espresso@ff.iij4u.or.jp

0 Comments:

Post a Comment

<< Home