vdr/osd.h

463 lines
24 KiB
C
Raw Normal View History

2000-02-19 13:36:48 +01:00
/*
* osd.h: Abstract On Screen Display layer
*
2000-04-24 09:46:05 +02:00
* See the main source file 'vdr.c' for copyright information and
2000-02-19 13:36:48 +01:00
* how to reach the author.
*
* $Id: osd.h 2.3 2009/05/03 13:52:10 kls Exp $
2000-02-19 13:36:48 +01:00
*/
#ifndef __OSD_H
#define __OSD_H
#include <limits.h>
2004-05-16 10:35:36 +02:00
#include <stdio.h>
#include <stdint.h>
2007-06-17 14:02:02 +02:00
#include "config.h"
2004-05-16 10:35:36 +02:00
#include "font.h"
#include "tools.h"
2000-02-19 13:36:48 +01:00
2007-10-12 14:52:30 +02:00
#define OSD_LEVEL_DEFAULT 0
#define OSD_LEVEL_SUBTITLES 10
2004-05-16 10:35:36 +02:00
#define MAXNUMCOLORS 256
2000-02-19 13:36:48 +01:00
2004-05-16 10:35:36 +02:00
enum {
//AARRGGBB
clrTransparent = 0x00000000,
clrGray50 = 0x7F000000, // 50% gray
clrBlack = 0xFF000000,
clrRed = 0xFFFC1414,
clrGreen = 0xFF24FC24,
clrYellow = 0xFFFCC024,
clrMagenta = 0xFFB000FC,
clrBlue = 0xFF0000FC,
clrCyan = 0xFF00FCFC,
clrWhite = 0xFFFCFCFC,
};
2000-02-19 13:36:48 +01:00
2004-05-16 10:35:36 +02:00
enum eOsdError { oeOk,
oeTooManyAreas,
oeTooManyColors,
oeBppNotSupported,
oeAreasOverlap,
oeWrongAlignment,
oeOutOfMemory,
oeWrongAreaSize,
2004-05-16 10:35:36 +02:00
oeUnknown,
};
typedef uint32_t tColor; // see also font.h
2004-05-16 10:35:36 +02:00
typedef uint8_t tIndex;
class cPalette {
private:
2004-05-16 10:35:36 +02:00
tColor color[MAXNUMCOLORS];
int bpp;
int maxColors, numColors;
bool modified;
double antiAliasGranularity;
2004-05-16 10:35:36 +02:00
protected:
typedef tIndex tIndexes[MAXNUMCOLORS];
public:
2004-05-16 10:35:36 +02:00
cPalette(int Bpp = 8);
///< Initializes the palette with the given color depth.
virtual ~cPalette();
void SetAntiAliasGranularity(uint FixedColors, uint BlendColors);
///< Allows the system to optimize utilization of the limited color
///< palette entries when generating blended colors for anti-aliasing.
///< FixedColors is the maximum number of colors used, and BlendColors
///< is the maximum number of foreground/background color combinations
///< used with anti-aliasing. If this function is not called with
///< useful values, the palette may be filled up with many shades of
///< a single color combination, and may not be able to serve all
///< requested colors. By default the palette assumes there will be
///< 10 fixed colors and 10 color combinations.
2007-10-12 14:52:30 +02:00
int Bpp(void) const { return bpp; }
2004-05-16 10:35:36 +02:00
void Reset(void);
///< Resets the palette, making it contain no colors.
int Index(tColor Color);
///< Returns the index of the given Color (the first color has index 0).
///< If Color is not yet contained in this palette, it will be added if
///< there is a free slot. If the color can't be added to this palette,
///< the closest existing color will be returned.
2007-10-12 14:52:30 +02:00
tColor Color(int Index) const { return Index < maxColors ? color[Index] : 0; }
2004-05-16 10:35:36 +02:00
///< Returns the color at the given Index. If Index is outside the valid
///< range, 0 will be returned.
void SetBpp(int Bpp);
///< Sets the color depth of this palette to the given value.
///< The palette contents will be reset, so that it contains no colors.
void SetColor(int Index, tColor Color);
///< Sets the palette entry at Index to Color. If Index is larger than
///< the number of currently used entries in this palette, the entries
///< in between will have undefined values.
2007-10-12 14:52:30 +02:00
const tColor *Colors(int &NumColors) const;
2004-05-16 10:35:36 +02:00
///< Returns a pointer to the complete color table and stores the
///< number of valid entries in NumColors. If no colors have been
///< stored yet, NumColors will be set to 0 and the function will
///< return NULL.
void Take(const cPalette &Palette, tIndexes *Indexes = NULL, tColor ColorFg = 0, tColor ColorBg = 0);
///< Takes the colors from the given Palette and adds them to this palette,
///< using existing entries if possible. If Indexes is given, it will be
///< filled with the index values that each color of Palette has in this
///< palette. If either of ColorFg or ColorBg is not zero, the first color
///< in Palette will be taken as ColorBg, and the second color will become
///< ColorFg.
void Replace(const cPalette &Palette);
///< Replaces the colors of this palette with the colors from the given
///< palette.
2007-10-12 14:52:30 +02:00
tColor Blend(tColor ColorFg, tColor ColorBg, uint8_t Level) const;
///< Determines a color that consists of a linear blend between ColorFg
///< and ColorBg. If Level is 0, the result is ColorBg, if it is 255,
///< the result is ColorFg. If SetAntiAliasGranularity() has been called previously,
///< Level will be mapped to a limited range of levels that allow to make best
///< use of the palette entries.
2007-10-12 14:52:30 +02:00
int ClosestColor(tColor Color, int MaxDiff = INT_MAX) const;
///< Returns the index of a color in this palette that is closest to the given
///< Color. MaxDiff can be used to control the maximum allowed color difference.
///< If no color with a maximum difference of MaxDiff can be found, -1 will
///< be returned. With the default value of INT_MAX, there will always be
///< a valid color index returned, but the color may be completely different.
};
2004-05-16 10:35:36 +02:00
enum eTextAlignment { taCenter = 0x00,
taLeft = 0x01,
taRight = 0x02,
taTop = 0x04,
taBottom = 0x08,
taDefault = taTop | taLeft
};
class cFont;
2004-05-16 10:35:36 +02:00
class cBitmap : public cPalette {
2000-02-19 13:36:48 +01:00
private:
2004-05-16 10:35:36 +02:00
tIndex *bitmap;
int x0, y0;
int width, height;
int dirtyX1, dirtyY1, dirtyX2, dirtyY2;
public:
cBitmap(int Width, int Height, int Bpp, int X0 = 0, int Y0 = 0);
///< Creates a bitmap with the given Width, Height and color depth (Bpp).
///< X0 and Y0 define the offset at which this bitmap will be located on the OSD.
///< All coordinates given in the other functions will be relative to
///< this offset (unless specified otherwise).
cBitmap(const char *FileName);
///< Creates a bitmap and loads an XPM image from the given file.
2006-02-05 13:55:58 +01:00
cBitmap(const char *const Xpm[]);
2004-05-16 10:35:36 +02:00
///< Creates a bitmap from the given XPM data.
virtual ~cBitmap();
int X0(void) const { return x0; }
int Y0(void) const { return y0; }
int Width(void) const { return width; }
int Height(void) const { return height; }
void SetSize(int Width, int Height);
///< Sets the size of this bitmap to the given values. Any previous
///< contents of the bitmap will be lost. If Width and Height are the same
///< as the current values, nothing will happen and the bitmap remains
///< unchanged.
bool Contains(int x, int y) const;
///< Returns true if this bitmap contains the point (x, y).
bool Covers(int x1, int y1, int x2, int y2) const;
///< Returns true if the rectangle defined by the given coordinates
///< completely covers this bitmap.
2004-05-16 10:35:36 +02:00
bool Intersects(int x1, int y1, int x2, int y2) const;
///< Returns true if the rectangle defined by the given coordinates
///< intersects with this bitmap.
2004-05-16 10:35:36 +02:00
bool Dirty(int &x1, int &y1, int &x2, int &y2);
///< Tells whether there is a dirty area and returns the bounding
///< rectangle of that area (relative to the bitmaps origin).
void Clean(void);
///< Marks the dirty area as clean.
bool LoadXpm(const char *FileName);
///< Calls SetXpm() with the data from the file FileName.
///< Returns true if the operation was successful.
2006-02-05 13:55:58 +01:00
bool SetXpm(const char *const Xpm[], bool IgnoreNone = false);
2004-05-16 10:35:36 +02:00
///< Sets this bitmap to the given XPM data. Any previous bitmap or
///< palette data will be overwritten with the new data.
///< If IgnoreNone is true, a "none" color entry will be ignored.
///< Only set IgnoreNone to true if you know that there is a "none"
///< color entry in the XPM data and that this entry is not used!
///< If SetXpm() is called with IgnoreNone set to false and the XPM
///< data contains an unused "none" entry, it will be automatically
///< called again with IgnoreNone set to true.
2004-05-16 10:35:36 +02:00
///< Returns true if the operation was successful.
void SetIndex(int x, int y, tIndex Index);
///< Sets the index at the given coordinates to Index.
///< Coordinates are relative to the bitmap's origin.
void DrawPixel(int x, int y, tColor Color);
///< Sets the pixel at the given coordinates to the given Color, which is
///< a full 32 bit ARGB value.
///< If the coordinates are outside the bitmap area, no pixel will be set.
void DrawBitmap(int x, int y, const cBitmap &Bitmap, tColor ColorFg = 0, tColor ColorBg = 0, bool ReplacePalette = false, bool Overlay = false);
2004-05-16 10:35:36 +02:00
///< Sets the pixels in this bitmap with the data from the given
///< Bitmap, putting the upper left corner of the Bitmap at (x, y).
///< If ColorFg or ColorBg is given, the first palette entry of the Bitmap
///< will be mapped to ColorBg and the second palette entry will be mapped to
///< ColorFg (palette indexes are defined so that 0 is the background and
2006-02-26 14:45:05 +01:00
///< 1 is the foreground color). ReplacePalette controls whether the target
///< area shall have its palette replaced with the one from Bitmap.
///< If Overlay is true, any pixel in Bitmap that has color index 0 will
///< not overwrite the corresponding pixel in the target area.
2004-05-16 10:35:36 +02:00
void DrawText(int x, int y, const char *s, tColor ColorFg, tColor ColorBg, const cFont *Font, int Width = 0, int Height = 0, int Alignment = taDefault);
///< Draws the given string at coordinates (x, y) with the given foreground
///< and background color and font. If Width and Height are given, the text
///< will be drawn into a rectangle with the given size and the given
///< Alignment (default is top-left). If ColorBg is clrTransparent, no
///< background pixels will be drawn, which allows drawing "transparent" text.
2004-05-16 10:35:36 +02:00
void DrawRectangle(int x1, int y1, int x2, int y2, tColor Color);
///< Draws a filled rectangle defined by the upper left (x1, y1) and lower right
///< (x2, y2) corners with the given Color. If the rectangle covers the entire
///< bitmap area, the color palette will be reset, so that new colors can be
///< used for drawing.
void DrawEllipse(int x1, int y1, int x2, int y2, tColor Color, int Quadrants = 0);
///< Draws a filled ellipse defined by the upper left (x1, y1) and lower right
///< (x2, y2) corners with the given Color. Quadrants controls which parts of
///< the ellipse are actually drawn:
///< 0 draws the entire ellipse
///< 1..4 draws only the first, second, third or fourth quadrant, respectively
///< 5..8 draws the right, top, left or bottom half, respectively
///< -1..-8 draws the inverted part of the given quadrant(s)
///< If Quadrants is not 0, the coordinates are those of the actual area, not
///< the full circle!
void DrawSlope(int x1, int y1, int x2, int y2, tColor Color, int Type);
///< Draws a "slope" into the rectangle defined by the upper left (x1, y1) and
///< lower right (x2, y2) corners with the given Color. Type controls the
///< direction of the slope and which side of it will be drawn:
///< 0: horizontal, rising, lower
///< 1: horizontal, rising, upper
///< 2: horizontal, falling, lower
///< 3: horizontal, falling, upper
///< 4: vertical, rising, lower
///< 5: vertical, rising, upper
///< 6: vertical, falling, lower
///< 7: vertical, falling, upper
const tIndex *Data(int x, int y) const;
2004-05-16 10:35:36 +02:00
///< Returns the address of the index byte at the given coordinates.
tColor GetColor(int x, int y) const { return Color(*Data(x, y)); }
///< Returns the color at the given coordinates.
2007-10-12 14:52:30 +02:00
void ReduceBpp(const cPalette &Palette);
///< Reduces the color depth of the bitmap to that of the given Palette.
///< If Palette's color depth is not smaller than the bitmap's current
///< color depth, or if it is not one of 4bpp or 2bpp, nothing happens. After
///< reducing the color depth the current palette is replaced with
///< the given one.
void ShrinkBpp(int NewBpp);
///< Shrinks the color depth of the bitmap to NewBpp by keeping only
///< the 2^NewBpp most frequently used colors as defined in the current palette.
///< If NewBpp is not smaller than the bitmap's current color depth,
///< or if it is not one of 4bpp or 2bpp, nothing happens.
2004-05-16 10:35:36 +02:00
};
struct tArea {
int x1, y1, x2, y2;
int bpp;
int Width(void) const { return x2 - x1 + 1; }
int Height(void) const { return y2 - y1 + 1; }
bool Intersects(const tArea &Area) const { return !(x2 < Area.x1 || x1 > Area.x2 || y2 < Area.y1 || y1 > Area.y2); }
};
#define MAXOSDAREAS 16
class cOsd {
2004-06-12 13:30:11 +02:00
friend class cOsdProvider;
2004-05-16 10:35:36 +02:00
private:
2007-06-17 14:02:02 +02:00
static int osdLeft, osdTop, osdWidth, osdHeight;
static cVector<cOsd *> Osds;
2004-05-16 10:35:36 +02:00
cBitmap *savedRegion;
cBitmap *bitmaps[MAXOSDAREAS];
int numBitmaps;
int left, top, width, height;
uint level;
bool active;
2004-06-12 13:30:11 +02:00
protected:
cOsd(int Left, int Top, uint Level);
2004-05-16 10:35:36 +02:00
///< Initializes the OSD with the given coordinates.
///< By default it is assumed that the full area will be able to display
///< full 32 bit graphics (ARGB with eight bit for each color and the alpha
///< value, repectively). However, the actual hardware in use may not be
///< able to display such a high resolution OSD, so there is an option to
///< divide the full OSD area into several sub-areas with lower color depths
///< and individual palettes. The sub-areas need not necessarily cover the
///< entire OSD area, but only the OSD area actually covered by sub-areas
///< will be available for drawing.
///< At least one area must be defined in order to set the actual width and
///< height of the OSD. Also, the caller must first try to use an area that
///< consists of only one sub-area that covers the entire drawing space,
///< and should require only the minimum necessary color depth. This is
///< because a derived cOsd class may or may not be able to handle more
///< than one area.
///< There can be any number of cOsd objects at the same time, but only
///< one of them will be active at any given time. The active OSD is the
///< one with the lowest value of Level. If there are several cOsd objects
///< with the same Level, the one that was created first will be active.
bool Active(void) { return active; }
virtual void SetActive(bool On) { active = On; }
///< Sets this OSD to be the active one.
///< A derived class must call cOsd::SetActive(On).
2004-06-12 13:30:11 +02:00
public:
2004-05-16 10:35:36 +02:00
virtual ~cOsd();
///< Shuts down the OSD.
2007-06-17 14:02:02 +02:00
static int OsdLeft(void) { return osdLeft ? osdLeft : Setup.OSDLeft; }
static int OsdTop(void) { return osdTop ? osdTop : Setup.OSDTop; }
static int OsdWidth(void) { return osdWidth ? osdWidth : Setup.OSDWidth; }
static int OsdHeight(void) { return osdHeight ? osdHeight : Setup.OSDHeight; }
static void SetOsdPosition(int Left, int Top, int Width, int Height);
2007-06-17 14:02:02 +02:00
///< Sets the position and size of the OSD to the given values.
///< This may be useful for plugins that determine the scaling of the
///< video image and need to scale the OSD accordingly to fit on the
///< screen.
2007-10-12 14:52:30 +02:00
static int IsOpen(void) { return Osds.Size() && Osds[0]->level == OSD_LEVEL_DEFAULT; }
///< Returns true if there is currently a level 0 OSD open.
2004-05-16 10:35:36 +02:00
int Left(void) { return left; }
int Top(void) { return top; }
int Width(void) { return width; }
int Height(void) { return height; }
void SetAntiAliasGranularity(uint FixedColors, uint BlendColors);
///< Allows the system to optimize utilization of the limited color
///< palette entries when generating blended colors for anti-aliasing.
///< FixedColors is the maximum number of colors used, and BlendColors
///< is the maximum number of foreground/background color combinations
///< used with anti-aliasing. If this function is not called with
///< useful values, the palette may be filled up with many shades of
///< a single color combination, and may not be able to serve all
///< requested colors. By default the palette assumes there will be
///< 10 fixed colors and 10 color combinations.
2004-05-16 10:35:36 +02:00
cBitmap *GetBitmap(int Area);
///< Returns a pointer to the bitmap for the given Area, or NULL if no
///< such bitmap exists.
virtual eOsdError CanHandleAreas(const tArea *Areas, int NumAreas);
///< Checks whether the OSD can display the given set of sub-areas.
///< The return value indicates whether a call to SetAreas() with this
///< set of areas will succeed. CanHandleAreas() may be called with an
///< OSD that is already in use with other areas and will not interfere
///< with the current operation of the OSD.
///< A derived class must first call the base class CanHandleAreas()
///< to check the basic conditions, like not overlapping etc.
virtual eOsdError SetAreas(const tArea *Areas, int NumAreas);
///< Sets the sub-areas to the given areas.
///< The return value indicates whether the operation was successful.
///< If an error is reported, nothing will have changed and the previous
///< OSD (if any) will still be displayed as before.
///< If the OSD has been divided into several sub-areas, all areas that
///< are part of the rectangle that surrounds a given drawing operation
///< will be drawn into, with the proper offsets.
2007-10-12 14:52:30 +02:00
///< A new call overwrites any previous settings
2004-05-16 10:35:36 +02:00
virtual void SaveRegion(int x1, int y1, int x2, int y2);
///< Saves the region defined by the given coordinates for later restoration
///< through RestoreRegion(). Only one saved region can be active at any
///< given time.
virtual void RestoreRegion(void);
///< Restores the region previously saved by a call to SaveRegion().
///< If SaveRegion() has not been called before, nothing will happen.
virtual eOsdError SetPalette(const cPalette &Palette, int Area);
///< Sets the Palette for the given Area (the first area is numbered 0).
virtual void DrawPixel(int x, int y, tColor Color);
///< Sets the pixel at the given coordinates to the given Color, which is
///< a full 32 bit ARGB value.
///< If the OSD area has been divided into separate sub-areas, and the
///< given coordinates don't fall into any of these sub-areas, no pixel will
///< be set.
virtual void DrawBitmap(int x, int y, const cBitmap &Bitmap, tColor ColorFg = 0, tColor ColorBg = 0, bool ReplacePalette = false, bool Overlay = false);
2004-05-16 10:35:36 +02:00
///< Sets the pixels in the OSD with the data from the given
///< Bitmap, putting the upper left corner of the Bitmap at (x, y).
///< If ColorFg or ColorBg is given, the first palette entry of the Bitmap
///< will be mapped to ColorBg and the second palette entry will be mapped to
///< ColorFg (palette indexes are defined so that 0 is the background and
2006-02-26 14:45:05 +01:00
///< 1 is the foreground color). ReplacePalette controls whether the target
///< area shall have its palette replaced with the one from Bitmap.
///< If Overlay is true, any pixel in Bitmap that has color index 0 will
///< not overwrite the corresponding pixel in the target area.
2004-05-16 10:35:36 +02:00
virtual void DrawText(int x, int y, const char *s, tColor ColorFg, tColor ColorBg, const cFont *Font, int Width = 0, int Height = 0, int Alignment = taDefault);
///< Draws the given string at coordinates (x, y) with the given foreground
///< and background color and font. If Width and Height are given, the text
///< will be drawn into a rectangle with the given size and the given
///< Alignment (default is top-left). If ColorBg is clrTransparent, no
///< background pixels will be drawn, which allows drawing "transparent" text.
2004-05-16 10:35:36 +02:00
virtual void DrawRectangle(int x1, int y1, int x2, int y2, tColor Color);
///< Draws a filled rectangle defined by the upper left (x1, y1) and lower right
///< (x2, y2) corners with the given Color.
virtual void DrawEllipse(int x1, int y1, int x2, int y2, tColor Color, int Quadrants = 0);
///< Draws a filled ellipse defined by the upper left (x1, y1) and lower right
///< (x2, y2) corners with the given Color. Quadrants controls which parts of
///< the ellipse are actually drawn:
///< 0 draws the entire ellipse
///< 1..4 draws only the first, second, third or fourth quadrant, respectively
///< 5..8 draws the right, top, left or bottom half, respectively
///< -1..-8 draws the inverted part of the given quadrant(s)
///< If Quadrants is not 0, the coordinates are those of the actual area, not
///< the full circle!
virtual void DrawSlope(int x1, int y1, int x2, int y2, tColor Color, int Type);
2004-05-16 10:35:36 +02:00
///< Draws a "slope" into the rectangle defined by the upper left (x1, y1) and
///< lower right (x2, y2) corners with the given Color. Type controls the
///< direction of the slope and which side of it will be drawn:
///< 0: horizontal, rising, lower
///< 1: horizontal, rising, upper
///< 2: horizontal, falling, lower
///< 3: horizontal, falling, upper
///< 4: vertical, rising, lower
///< 5: vertical, rising, upper
///< 6: vertical, falling, lower
///< 7: vertical, falling, upper
virtual void Flush(void);
///< Actually commits all data to the OSD hardware.
2002-11-03 18:03:55 +01:00
};
2000-02-19 13:36:48 +01:00
2004-05-16 10:35:36 +02:00
class cOsdProvider {
2002-11-24 10:45:39 +01:00
private:
2004-05-16 10:35:36 +02:00
static cOsdProvider *osdProvider;
static int oldWidth;
static int oldHeight;
static int oldAspect;
2000-04-30 10:22:13 +02:00
protected:
virtual cOsd *CreateOsd(int Left, int Top, uint Level) = 0;
2004-05-16 10:35:36 +02:00
///< Returns a pointer to a newly created cOsd object, which will be located
///< at the given coordinates.
public:
2004-05-16 10:35:36 +02:00
cOsdProvider(void);
//XXX maybe parameter to make this one "sticky"??? (frame-buffer etc.)
virtual ~cOsdProvider();
2007-10-12 14:52:30 +02:00
static cOsd *NewOsd(int Left, int Top, uint Level = OSD_LEVEL_DEFAULT);
2004-05-16 10:35:36 +02:00
///< Returns a pointer to a newly created cOsd object, which will be located
///< at the given coordinates. When the cOsd object is no longer needed, the
///< caller must delete it. If the OSD is already in use, or there is no OSD
///< provider, a dummy OSD is returned so that the caller may always use the
///< returned pointer without having to check it every time it is accessed.
static void UpdateOsdSize(bool Force = false);
///< Inquires the actual size of the video display and adjusts the OSD and
///< font sizes accordingly. If Force is true, all settings are recalculated,
///< even if the video resolution hasn't changed since the last call to
///< this funtion.
2004-05-16 10:35:36 +02:00
static void Shutdown(void);
///< Shuts down the OSD provider facility by deleting the current OSD provider.
2002-11-03 18:03:55 +01:00
};
2004-05-16 10:35:36 +02:00
class cTextScroller {
2000-02-19 13:36:48 +01:00
private:
2004-05-16 10:35:36 +02:00
cOsd *osd;
int left, top, width, height;
const cFont *font;
tColor colorFg, colorBg;
int offset, shown;
cTextWrapper textWrapper;
void DrawText(void);
2000-02-19 13:36:48 +01:00
public:
2004-05-16 10:35:36 +02:00
cTextScroller(void);
cTextScroller(cOsd *Osd, int Left, int Top, int Width, int Height, const char *Text, const cFont *Font, tColor ColorFg, tColor ColorBg);
void Set(cOsd *Osd, int Left, int Top, int Width, int Height, const char *Text, const cFont *Font, tColor ColorFg, tColor ColorBg);
void Reset(void);
int Left(void) { return left; }
int Top(void) { return top; }
int Width(void) { return width; }
int Height(void) { return height; }
int Total(void) { return textWrapper.Lines(); }
int Offset(void) { return offset; }
int Shown(void) { return shown; }
bool CanScroll(void) { return CanScrollUp() || CanScrollDown(); }
bool CanScrollUp(void) { return offset > 0; }
bool CanScrollDown(void) { return offset + shown < Total(); }
void Scroll(bool Up, bool Page);
2000-02-19 13:36:48 +01:00
};
#endif //__OSD_H